From e1375f82fe889d7c58d768c2c14974caa07f2b81 Mon Sep 17 00:00:00 2001 From: mikalai_kuzniatsou Date: Mon, 17 Apr 2017 03:35:55 +0300 Subject: [PATCH 1/4] lesson13. task complete --- Vagrantfile | 56 + [agent]-node1-puppet.conf | 5 + [agent]-node2-puppet.conf | 6 + environments/prod/environment.conf | 19 + environments/prod/manifests/site.pp | 17 + environments/prod/modules/mysql/CHANGELOG.md | 770 +++++++ .../prod/modules/mysql/CONTRIBUTING.md | 218 ++ environments/prod/modules/mysql/Gemfile | 52 + environments/prod/modules/mysql/LICENSE | 202 ++ environments/prod/modules/mysql/NOTICE | 18 + environments/prod/modules/mysql/README.md | 1050 ++++++++++ environments/prod/modules/mysql/Rakefile | 38 + environments/prod/modules/mysql/TODO | 8 + .../prod/modules/mysql/checksums.json | 115 ++ .../prod/modules/mysql/examples/backup.pp | 9 + .../prod/modules/mysql/examples/bindings.pp | 3 + .../prod/modules/mysql/examples/java.pp | 1 + .../modules/mysql/examples/mysql_database.pp | 17 + .../prod/modules/mysql/examples/mysql_db.pp | 17 + .../modules/mysql/examples/mysql_grant.pp | 5 + .../modules/mysql/examples/mysql_plugin.pp | 23 + .../prod/modules/mysql/examples/mysql_user.pp | 32 + .../prod/modules/mysql/examples/perl.pp | 1 + .../prod/modules/mysql/examples/python.pp | 1 + .../prod/modules/mysql/examples/ruby.pp | 1 + .../prod/modules/mysql/examples/server.pp | 3 + .../mysql/examples/server/account_security.pp | 4 + .../modules/mysql/examples/server/config.pp | 3 + .../mysql/lib/facter/mysql_server_id.rb | 9 + .../modules/mysql/lib/facter/mysql_version.rb | 8 + .../mysql/lib/facter/mysqld_version.rb | 5 + .../parser/functions/mysql_deepmerge.rb | 58 + .../puppet/parser/functions/mysql_dirname.rb | 15 + .../puppet/parser/functions/mysql_password.rb | 17 + .../parser/functions/mysql_strip_hash.rb | 21 + .../mysql/lib/puppet/provider/mysql.rb | 115 ++ .../puppet/provider/mysql_database/mysql.rb | 68 + .../puppet/provider/mysql_datadir/mysql.rb | 78 + .../lib/puppet/provider/mysql_grant/mysql.rb | 176 ++ .../lib/puppet/provider/mysql_plugin/mysql.rb | 53 + .../lib/puppet/provider/mysql_user/mysql.rb | 197 ++ .../mysql/lib/puppet/type/mysql_database.rb | 25 + .../mysql/lib/puppet/type/mysql_datadir.rb | 35 + .../mysql/lib/puppet/type/mysql_grant.rb | 103 + .../mysql/lib/puppet/type/mysql_plugin.rb | 17 + .../mysql/lib/puppet/type/mysql_user.rb | 105 + .../mysql/manifests/backup/mysqlbackup.pp | 106 + .../mysql/manifests/backup/mysqldump.pp | 73 + .../mysql/manifests/backup/xtrabackup.pp | 67 + .../prod/modules/mysql/manifests/bindings.pp | 57 + .../mysql/manifests/bindings/client_dev.pp | 15 + .../mysql/manifests/bindings/daemon_dev.pp | 15 + .../modules/mysql/manifests/bindings/java.pp | 11 + .../modules/mysql/manifests/bindings/perl.pp | 11 + .../modules/mysql/manifests/bindings/php.pp | 11 + .../mysql/manifests/bindings/python.pp | 11 + .../modules/mysql/manifests/bindings/ruby.pp | 11 + .../prod/modules/mysql/manifests/client.pp | 29 + .../modules/mysql/manifests/client/install.pp | 14 + .../prod/modules/mysql/manifests/db.pp | 72 + .../prod/modules/mysql/manifests/params.pp | 450 +++++ .../prod/modules/mysql/manifests/server.pp | 89 + .../manifests/server/account_security.pp | 39 + .../modules/mysql/manifests/server/backup.pp | 55 + .../mysql/manifests/server/binarylog.pp | 22 + .../modules/mysql/manifests/server/config.pp | 56 + .../modules/mysql/manifests/server/install.pp | 13 + .../mysql/manifests/server/installdb.pp | 46 + .../modules/mysql/manifests/server/monitor.pp | 24 + .../mysql/manifests/server/mysqltuner.pp | 52 + .../mysql/manifests/server/providers.pp | 8 + .../mysql/manifests/server/root_password.pp | 46 + .../modules/mysql/manifests/server/service.pp | 58 + environments/prod/modules/mysql/metadata.json | 80 + .../spec/acceptance/mysql_backup_spec.rb | 205 ++ .../mysql/spec/acceptance/mysql_db_spec.rb | 65 + .../mysql/spec/acceptance/mysql_helper.rb | 8 + .../spec/acceptance/mysql_server_spec.rb | 107 + .../spec/acceptance/nodesets/centos-7-x64.yml | 10 + .../spec/acceptance/nodesets/debian-8-x64.yml | 10 + .../spec/acceptance/nodesets/default.yml | 10 + .../acceptance/nodesets/docker/centos-7.yml | 12 + .../acceptance/nodesets/docker/debian-8.yml | 11 + .../nodesets/docker/ubuntu-14.04.yml | 12 + .../acceptance/types/mysql_database_spec.rb | 64 + .../spec/acceptance/types/mysql_grant_spec.rb | 578 ++++++ .../acceptance/types/mysql_plugin_spec.rb | 76 + .../spec/acceptance/types/mysql_user_spec.rb | 146 ++ .../spec/classes/graceful_failures_spec.rb | 19 + .../mysql/spec/classes/mycnf_template_spec.rb | 86 + .../mysql/spec/classes/mysql_bindings_spec.rb | 32 + .../mysql/spec/classes/mysql_client_spec.rb | 38 + .../mysql_server_account_security_spec.rb | 86 + .../spec/classes/mysql_server_backup_spec.rb | 407 ++++ .../spec/classes/mysql_server_monitor_spec.rb | 38 + .../classes/mysql_server_mysqltuner_spec.rb | 46 + .../mysql/spec/classes/mysql_server_spec.rb | 220 ++ .../mysql/spec/defines/mysql_db_spec.rb | 83 + .../prod/modules/mysql/spec/spec.opts | 6 + .../prod/modules/mysql/spec/spec_helper.rb | 8 + .../mysql/spec/spec_helper_acceptance.rb | 66 + .../modules/mysql/spec/spec_helper_local.rb | 3 + .../spec/unit/facter/mysql_server_id_spec.rb | 27 + .../spec/unit/facter/mysql_version_spec.rb | 20 + .../spec/unit/facter/mysqld_version_spec.rb | 20 + .../puppet/functions/mysql_deepmerge_spec.rb | 91 + .../puppet/functions/mysql_password_spec.rb | 37 + .../provider/mysql_database/mysql_spec.rb | 118 ++ .../provider/mysql_plugin/mysql_spec.rb | 71 + .../puppet/provider/mysql_user/mysql_spec.rb | 345 ++++ .../unit/puppet/type/mysql_database_spec.rb | 29 + .../spec/unit/puppet/type/mysql_grant_spec.rb | 74 + .../unit/puppet/type/mysql_plugin_spec.rb | 24 + .../spec/unit/puppet/type/mysql_user_spec.rb | 136 ++ .../prod/modules/mysql/templates/meb.cnf.erb | 18 + .../prod/modules/mysql/templates/my.cnf.erb | 24 + .../modules/mysql/templates/my.cnf.pass.erb | 11 + .../mysql/templates/mysqlbackup.sh.erb | 104 + .../modules/mysql/templates/xtrabackup.sh.erb | 21 + environments/prod/modules/stdlib/CHANGELOG.md | 775 +++++++ .../prod/modules/stdlib/CONTRIBUTING.md | 217 ++ environments/prod/modules/stdlib/Gemfile | 86 + environments/prod/modules/stdlib/LICENSE | 202 ++ .../prod/modules/stdlib/MAINTAINERS.md | 6 + environments/prod/modules/stdlib/NOTICE | 23 + .../prod/modules/stdlib/README.markdown | 1791 +++++++++++++++++ .../modules/stdlib/README_DEVELOPER.markdown | 35 + .../prod/modules/stdlib/README_SPECS.markdown | 7 + .../modules/stdlib/RELEASE_PROCESS.markdown | 24 + environments/prod/modules/stdlib/Rakefile | 37 + environments/prod/modules/stdlib/appveyor.yml | 40 + .../prod/modules/stdlib/checksums.json | 508 +++++ .../prod/modules/stdlib/examples/file_line.pp | 9 + .../stdlib/examples/has_interface_with.pp | 9 + .../modules/stdlib/examples/has_ip_address.pp | 3 + .../modules/stdlib/examples/has_ip_network.pp | 3 + .../prod/modules/stdlib/examples/init.pp | 1 + .../modules/stdlib/lib/facter/facter_dot_d.rb | 202 ++ .../stdlib/lib/facter/package_provider.rb | 21 + .../modules/stdlib/lib/facter/pe_version.rb | 58 + .../stdlib/lib/facter/puppet_settings.rb | 43 + .../modules/stdlib/lib/facter/root_home.rb | 45 + .../stdlib/lib/facter/service_provider.rb | 17 + .../stdlib/lib/facter/util/puppet_settings.rb | 21 + .../lib/puppet/functions/deprecation.rb | 29 + .../stdlib/lib/puppet/functions/is_a.rb | 32 + .../lib/puppet/functions/is_absolute_path.rb | 15 + .../stdlib/lib/puppet/functions/is_array.rb | 15 + .../stdlib/lib/puppet/functions/is_bool.rb | 15 + .../stdlib/lib/puppet/functions/is_float.rb | 15 + .../lib/puppet/functions/is_ip_address.rb | 15 + .../lib/puppet/functions/is_ipv4_address.rb | 15 + .../lib/puppet/functions/is_ipv6_address.rb | 15 + .../stdlib/lib/puppet/functions/is_numeric.rb | 15 + .../stdlib/lib/puppet/functions/is_string.rb | 15 + .../stdlib/lib/puppet/functions/length.rb | 14 + .../stdlib/lib/puppet/functions/type_of.rb | 19 + .../functions/validate_absolute_path.rb | 15 + .../lib/puppet/functions/validate_array.rb | 15 + .../lib/puppet/functions/validate_bool.rb | 15 + .../lib/puppet/functions/validate_hash.rb | 15 + .../lib/puppet/functions/validate_integer.rb | 15 + .../puppet/functions/validate_ip_address.rb | 15 + .../puppet/functions/validate_ipv4_address.rb | 15 + .../puppet/functions/validate_ipv6_address.rb | 15 + .../lib/puppet/functions/validate_legacy.rb | 62 + .../lib/puppet/functions/validate_numeric.rb | 15 + .../lib/puppet/functions/validate_re.rb | 15 + .../lib/puppet/functions/validate_slength.rb | 15 + .../lib/puppet/functions/validate_string.rb | 15 + .../stdlib/lib/puppet/parser/functions/abs.rb | 34 + .../lib/puppet/parser/functions/any2array.rb | 33 + .../lib/puppet/parser/functions/any2bool.rb | 54 + .../puppet/parser/functions/assert_private.rb | 28 + .../lib/puppet/parser/functions/base64.rb | 71 + .../lib/puppet/parser/functions/basename.rb | 34 + .../lib/puppet/parser/functions/bool2num.rb | 25 + .../lib/puppet/parser/functions/bool2str.rb | 44 + .../lib/puppet/parser/functions/camelcase.rb | 32 + .../lib/puppet/parser/functions/capitalize.rb | 30 + .../lib/puppet/parser/functions/ceiling.rb | 22 + .../lib/puppet/parser/functions/chomp.rb | 32 + .../lib/puppet/parser/functions/chop.rb | 34 + .../lib/puppet/parser/functions/clamp.rb | 29 + .../lib/puppet/parser/functions/concat.rb | 40 + .../puppet/parser/functions/convert_base.rb | 35 + .../lib/puppet/parser/functions/count.rb | 21 + .../lib/puppet/parser/functions/deep_merge.rb | 44 + .../parser/functions/defined_with_params.rb | 38 + .../lib/puppet/parser/functions/delete.rb | 43 + .../lib/puppet/parser/functions/delete_at.rb | 46 + .../puppet/parser/functions/delete_regex.rb | 44 + .../parser/functions/delete_undef_values.rb | 31 + .../puppet/parser/functions/delete_values.rb | 23 + .../puppet/parser/functions/deprecation.rb | 16 + .../lib/puppet/parser/functions/difference.rb | 35 + .../stdlib/lib/puppet/parser/functions/dig.rb | 16 + .../lib/puppet/parser/functions/dig44.rb | 70 + .../lib/puppet/parser/functions/dirname.rb | 21 + .../lib/puppet/parser/functions/dos2unix.rb | 15 + .../lib/puppet/parser/functions/downcase.rb | 31 + .../lib/puppet/parser/functions/empty.rb | 29 + .../puppet/parser/functions/enclose_ipv6.rb | 42 + .../parser/functions/ensure_packages.rb | 47 + .../parser/functions/ensure_resource.rb | 46 + .../parser/functions/ensure_resources.rb | 54 + .../lib/puppet/parser/functions/flatten.rb | 32 + .../lib/puppet/parser/functions/floor.rb | 22 + .../parser/functions/fqdn_rand_string.rb | 34 + .../puppet/parser/functions/fqdn_rotate.rb | 61 + .../lib/puppet/parser/functions/fqdn_uuid.rb | 92 + .../parser/functions/get_module_path.rb | 17 + .../lib/puppet/parser/functions/getparam.rb | 35 + .../lib/puppet/parser/functions/getvar.rb | 35 + .../lib/puppet/parser/functions/grep.rb | 32 + .../parser/functions/has_interface_with.rb | 70 + .../puppet/parser/functions/has_ip_address.rb | 24 + .../puppet/parser/functions/has_ip_network.rb | 24 + .../lib/puppet/parser/functions/has_key.rb | 28 + .../lib/puppet/parser/functions/hash.rb | 39 + .../puppet/parser/functions/intersection.rb | 33 + .../parser/functions/is_absolute_path.rb | 50 + .../lib/puppet/parser/functions/is_array.rb | 21 + .../lib/puppet/parser/functions/is_bool.rb | 21 + .../puppet/parser/functions/is_domain_name.rb | 53 + .../parser/functions/is_email_address.rb | 20 + .../lib/puppet/parser/functions/is_float.rb | 31 + .../parser/functions/is_function_available.rb | 25 + .../lib/puppet/parser/functions/is_hash.rb | 21 + .../lib/puppet/parser/functions/is_integer.rb | 46 + .../puppet/parser/functions/is_ip_address.rb | 33 + .../parser/functions/is_ipv4_address.rb | 29 + .../parser/functions/is_ipv6_address.rb | 29 + .../puppet/parser/functions/is_mac_address.rb | 26 + .../lib/puppet/parser/functions/is_numeric.rb | 74 + .../lib/puppet/parser/functions/is_string.rb | 28 + .../lib/puppet/parser/functions/join.rb | 40 + .../parser/functions/join_keys_to_values.rb | 53 + .../lib/puppet/parser/functions/keys.rb | 25 + .../parser/functions/load_module_metadata.rb | 24 + .../lib/puppet/parser/functions/loadjson.rb | 34 + .../lib/puppet/parser/functions/loadyaml.rb | 34 + .../lib/puppet/parser/functions/lstrip.rb | 30 + .../stdlib/lib/puppet/parser/functions/max.rb | 20 + .../lib/puppet/parser/functions/member.rb | 60 + .../lib/puppet/parser/functions/merge.rb | 34 + .../stdlib/lib/puppet/parser/functions/min.rb | 20 + .../lib/puppet/parser/functions/num2bool.rb | 42 + .../lib/puppet/parser/functions/parsejson.rb | 29 + .../lib/puppet/parser/functions/parseyaml.rb | 33 + .../lib/puppet/parser/functions/pick.rb | 29 + .../puppet/parser/functions/pick_default.rb | 35 + .../lib/puppet/parser/functions/prefix.rb | 51 + .../lib/puppet/parser/functions/private.rb | 17 + .../stdlib/lib/puppet/parser/functions/pry.rb | 30 + .../lib/puppet/parser/functions/pw_hash.rb | 59 + .../lib/puppet/parser/functions/range.rb | 85 + .../puppet/parser/functions/regexpescape.rb | 29 + .../lib/puppet/parser/functions/reject.rb | 31 + .../lib/puppet/parser/functions/reverse.rb | 25 + .../lib/puppet/parser/functions/rstrip.rb | 29 + .../puppet/parser/functions/seeded_rand.rb | 22 + .../puppet/parser/functions/shell_escape.rb | 29 + .../lib/puppet/parser/functions/shell_join.rb | 30 + .../puppet/parser/functions/shell_split.rb | 25 + .../lib/puppet/parser/functions/shuffle.rb | 43 + .../lib/puppet/parser/functions/size.rb | 46 + .../lib/puppet/parser/functions/sort.rb | 27 + .../lib/puppet/parser/functions/squeeze.rb | 35 + .../lib/puppet/parser/functions/str2bool.rb | 44 + .../parser/functions/str2saltedsha512.rb | 31 + .../lib/puppet/parser/functions/strftime.rb | 106 + .../lib/puppet/parser/functions/strip.rb | 36 + .../lib/puppet/parser/functions/suffix.rb | 52 + .../lib/puppet/parser/functions/swapcase.rb | 37 + .../lib/puppet/parser/functions/time.rb | 49 + .../lib/puppet/parser/functions/to_bytes.rb | 30 + .../puppet/parser/functions/try_get_value.rb | 53 + .../lib/puppet/parser/functions/type.rb | 19 + .../lib/puppet/parser/functions/type3x.rb | 50 + .../lib/puppet/parser/functions/union.rb | 28 + .../lib/puppet/parser/functions/unique.rb | 48 + .../lib/puppet/parser/functions/unix2dos.rb | 15 + .../lib/puppet/parser/functions/upcase.rb | 44 + .../lib/puppet/parser/functions/uriescape.rb | 33 + .../functions/validate_absolute_path.rb | 54 + .../puppet/parser/functions/validate_array.rb | 35 + .../parser/functions/validate_augeas.rb | 83 + .../puppet/parser/functions/validate_bool.rb | 37 + .../puppet/parser/functions/validate_cmd.rb | 63 + .../functions/validate_email_address.rb | 31 + .../puppet/parser/functions/validate_hash.rb | 35 + .../parser/functions/validate_integer.rb | 134 ++ .../parser/functions/validate_ip_address.rb | 52 + .../parser/functions/validate_ipv4_address.rb | 50 + .../parser/functions/validate_ipv6_address.rb | 51 + .../parser/functions/validate_numeric.rb | 96 + .../puppet/parser/functions/validate_re.rb | 50 + .../parser/functions/validate_slength.rb | 71 + .../parser/functions/validate_string.rb | 41 + .../functions/validate_x509_rsa_key_pair.rb | 47 + .../lib/puppet/parser/functions/values.rb | 38 + .../lib/puppet/parser/functions/values_at.rb | 93 + .../stdlib/lib/puppet/parser/functions/zip.rb | 38 + .../lib/puppet/provider/file_line/ruby.rb | 132 ++ .../modules/stdlib/lib/puppet/type/anchor.rb | 46 + .../stdlib/lib/puppet/type/file_line.rb | 146 ++ .../prod/modules/stdlib/manifests/init.pp | 18 + .../prod/modules/stdlib/manifests/stages.pp | 43 + .../prod/modules/stdlib/metadata.json | 112 ++ .../stdlib/spec/acceptance/abs_spec.rb | 30 + .../stdlib/spec/acceptance/anchor_spec.rb | 26 + .../stdlib/spec/acceptance/any2array_spec.rb | 49 + .../stdlib/spec/acceptance/base64_spec.rb | 18 + .../stdlib/spec/acceptance/bool2num_spec.rb | 34 + .../stdlib/spec/acceptance/build_csv.rb | 83 + .../stdlib/spec/acceptance/capitalize_spec.rb | 33 + .../stdlib/spec/acceptance/ceiling_spec.rb | 39 + .../stdlib/spec/acceptance/chomp_spec.rb | 21 + .../stdlib/spec/acceptance/chop_spec.rb | 45 + .../stdlib/spec/acceptance/clamp_spec.rb | 40 + .../stdlib/spec/acceptance/concat_spec.rb | 54 + .../stdlib/spec/acceptance/count_spec.rb | 30 + .../stdlib/spec/acceptance/deep_merge_spec.rb | 20 + .../acceptance/defined_with_params_spec.rb | 22 + .../stdlib/spec/acceptance/delete_at_spec.rb | 19 + .../stdlib/spec/acceptance/delete_spec.rb | 19 + .../acceptance/delete_undef_values_spec.rb | 19 + .../spec/acceptance/delete_values_spec.rb | 25 + .../spec/acceptance/deprecation_spec.rb | 102 + .../stdlib/spec/acceptance/difference_spec.rb | 26 + .../stdlib/spec/acceptance/dirname_spec.rb | 42 + .../stdlib/spec/acceptance/downcase_spec.rb | 39 + .../stdlib/spec/acceptance/empty_spec.rb | 53 + .../spec/acceptance/ensure_resource_spec.rb | 30 + .../stdlib/spec/acceptance/flatten_spec.rb | 39 + .../stdlib/spec/acceptance/floor_spec.rb | 39 + .../spec/acceptance/fqdn_rand_string_spec.rb | 66 + .../spec/acceptance/fqdn_rotate_spec.rb | 64 + .../spec/acceptance/get_module_path_spec.rb | 27 + .../stdlib/spec/acceptance/getparam_spec.rb | 24 + .../stdlib/spec/acceptance/getvar_spec.rb | 26 + .../stdlib/spec/acceptance/grep_spec.rb | 26 + .../acceptance/has_interface_with_spec.rb | 54 + .../spec/acceptance/has_ip_address_spec.rb | 33 + .../spec/acceptance/has_ip_network_spec.rb | 33 + .../stdlib/spec/acceptance/has_key_spec.rb | 41 + .../stdlib/spec/acceptance/hash_spec.rb | 26 + .../spec/acceptance/intersection_spec.rb | 27 + .../stdlib/spec/acceptance/is_a_spec.rb | 30 + .../stdlib/spec/acceptance/is_array_spec.rb | 67 + .../stdlib/spec/acceptance/is_bool_spec.rb | 81 + .../spec/acceptance/is_domain_name_spec.rb | 83 + .../stdlib/spec/acceptance/is_float_spec.rb | 86 + .../acceptance/is_function_available_spec.rb | 58 + .../stdlib/spec/acceptance/is_hash_spec.rb | 63 + .../stdlib/spec/acceptance/is_integer_spec.rb | 95 + .../spec/acceptance/is_ip_address_spec.rb | 80 + .../spec/acceptance/is_ipv4_address_spec.rb | 52 + .../spec/acceptance/is_ipv6_address_spec.rb | 66 + .../spec/acceptance/is_mac_address_spec.rb | 38 + .../stdlib/spec/acceptance/is_numeric_spec.rb | 95 + .../stdlib/spec/acceptance/is_string_spec.rb | 113 ++ .../acceptance/join_keys_to_values_spec.rb | 24 + .../stdlib/spec/acceptance/join_spec.rb | 26 + .../stdlib/spec/acceptance/keys_spec.rb | 23 + .../stdlib/spec/acceptance/loadjson_spec.rb | 52 + .../stdlib/spec/acceptance/loadyaml_spec.rb | 56 + .../stdlib/spec/acceptance/lstrip_spec.rb | 34 + .../stdlib/spec/acceptance/max_spec.rb | 20 + .../stdlib/spec/acceptance/member_spec.rb | 54 + .../stdlib/spec/acceptance/merge_spec.rb | 23 + .../stdlib/spec/acceptance/min_spec.rb | 20 + .../spec/acceptance/nodesets/centos-7-x64.yml | 10 + .../spec/acceptance/nodesets/debian-8-x64.yml | 10 + .../spec/acceptance/nodesets/default.yml | 10 + .../acceptance/nodesets/docker/centos-7.yml | 12 + .../acceptance/nodesets/docker/debian-8.yml | 11 + .../nodesets/docker/ubuntu-14.04.yml | 12 + .../stdlib/spec/acceptance/num2bool_spec.rb | 76 + .../stdlib/spec/acceptance/parsejson_spec.rb | 55 + .../stdlib/spec/acceptance/parseyaml_spec.rb | 58 + .../spec/acceptance/pick_default_spec.rb | 54 + .../stdlib/spec/acceptance/pick_spec.rb | 44 + .../stdlib/spec/acceptance/prefix_spec.rb | 42 + .../stdlib/spec/acceptance/pw_hash_spec.rb | 34 + .../stdlib/spec/acceptance/range_spec.rb | 36 + .../stdlib/spec/acceptance/reject_spec.rb | 42 + .../stdlib/spec/acceptance/reverse_spec.rb | 23 + .../stdlib/spec/acceptance/rstrip_spec.rb | 34 + .../stdlib/spec/acceptance/shuffle_spec.rb | 34 + .../stdlib/spec/acceptance/size_spec.rb | 55 + .../stdlib/spec/acceptance/sort_spec.rb | 34 + .../stdlib/spec/acceptance/squeeze_spec.rb | 47 + .../stdlib/spec/acceptance/str2bool_spec.rb | 31 + .../spec/acceptance/str2saltedsha512_spec.rb | 22 + .../stdlib/spec/acceptance/strftime_spec.rb | 22 + .../stdlib/spec/acceptance/strip_spec.rb | 34 + .../stdlib/spec/acceptance/suffix_spec.rb | 42 + .../stdlib/spec/acceptance/swapcase_spec.rb | 22 + .../stdlib/spec/acceptance/time_spec.rb | 36 + .../stdlib/spec/acceptance/to_bytes_spec.rb | 27 + .../spec/acceptance/try_get_value_spec.rb | 47 + .../stdlib/spec/acceptance/type_spec.rb | 37 + .../stdlib/spec/acceptance/union_spec.rb | 25 + .../stdlib/spec/acceptance/unique_spec.rb | 33 + .../stdlib/spec/acceptance/upcase_spec.rb | 33 + .../stdlib/spec/acceptance/uriescape_spec.rb | 23 + .../acceptance/validate_absolute_path_spec.rb | 31 + .../spec/acceptance/validate_array_spec.rb | 37 + .../spec/acceptance/validate_augeas_spec.rb | 63 + .../spec/acceptance/validate_bool_spec.rb | 37 + .../spec/acceptance/validate_cmd_spec.rb | 52 + .../spec/acceptance/validate_hash_spec.rb | 37 + .../acceptance/validate_ipv4_address_spec.rb | 31 + .../acceptance/validate_ipv6_address_spec.rb | 31 + .../spec/acceptance/validate_re_spec.rb | 47 + .../spec/acceptance/validate_slength_spec.rb | 72 + .../spec/acceptance/validate_string_spec.rb | 43 + .../stdlib/spec/acceptance/values_at_spec.rb | 73 + .../stdlib/spec/acceptance/values_spec.rb | 30 + .../stdlib/spec/acceptance/zip_spec.rb | 67 + .../stdlib/spec/aliases/absolute_path_spec.rb | 67 + .../stdlib/spec/aliases/absolutepath_spec.rb | 50 + .../modules/stdlib/spec/aliases/array_spec.rb | 34 + .../modules/stdlib/spec/aliases/bool_spec.rb | 32 + .../modules/stdlib/spec/aliases/float_spec.rb | 28 + .../modules/stdlib/spec/aliases/hash_spec.rb | 32 + .../stdlib/spec/aliases/httpsurl_spec.rb | 44 + .../stdlib/spec/aliases/httpurl_spec.rb | 47 + .../stdlib/spec/aliases/integer_spec.rb | 28 + .../modules/stdlib/spec/aliases/ip_address.rb | 34 + .../modules/stdlib/spec/aliases/ipv4_spec.rb | 22 + .../modules/stdlib/spec/aliases/ipv6_spec.rb | 40 + .../stdlib/spec/aliases/numeric_spec.rb | 32 + .../stdlib/spec/aliases/string_spec.rb | 32 + .../stdlib/spec/aliases/unixpath_spec.rb | 47 + .../stdlib/spec/aliases/windowspath_spec.rb | 47 + .../stdlib/spec/fixtures/dscacheutil/root | 8 + .../modules/stdlib/spec/fixtures/lsuser/root | 2 + .../fixtures/test/manifests/absolute_path.pp | 6 + .../fixtures/test/manifests/absolutepath.pp | 6 + .../spec/fixtures/test/manifests/array.pp | 8 + .../spec/fixtures/test/manifests/bool.pp | 8 + .../test/manifests/ensure_resources.pp | 4 + .../spec/fixtures/test/manifests/float.pp | 8 + .../spec/fixtures/test/manifests/hash.pp | 8 + .../spec/fixtures/test/manifests/httpsurl.pp | 6 + .../spec/fixtures/test/manifests/httpurl.pp | 6 + .../spec/fixtures/test/manifests/integer.pp | 8 + .../fixtures/test/manifests/ip_address.pp | 6 + .../spec/fixtures/test/manifests/ipv4.pp | 6 + .../spec/fixtures/test/manifests/ipv6.pp | 6 + .../spec/fixtures/test/manifests/numeric.pp | 8 + .../spec/fixtures/test/manifests/string.pp | 8 + .../spec/fixtures/test/manifests/unixpath.pp | 6 + .../fixtures/test/manifests/windowspath.pp | 6 + .../modules/stdlib/spec/functions/abs_spec.rb | 30 + .../stdlib/spec/functions/any2array_spec.rb | 21 + .../stdlib/spec/functions/any2bool_spec.rb | 42 + .../spec/functions/assert_private_spec.rb | 41 + .../stdlib/spec/functions/base64_spec.rb | 37 + .../stdlib/spec/functions/basename_spec.rb | 19 + .../stdlib/spec/functions/bool2num_spec.rb | 14 + .../stdlib/spec/functions/bool2str_spec.rb | 17 + .../stdlib/spec/functions/camelcase_spec.rb | 17 + .../stdlib/spec/functions/capitalize_spec.rb | 15 + .../stdlib/spec/functions/ceiling_spec.rb | 13 + .../stdlib/spec/functions/chomp_spec.rb | 25 + .../stdlib/spec/functions/chop_spec.rb | 25 + .../stdlib/spec/functions/clamp_spec.rb | 16 + .../stdlib/spec/functions/concat_spec.rb | 29 + .../spec/functions/convert_base_spec.rb | 24 + .../stdlib/spec/functions/count_spec.rb | 23 + .../stdlib/spec/functions/deep_merge_spec.rb | 59 + .../functions/defined_with_params_spec.rb | 40 + .../stdlib/spec/functions/delete_at_spec.rb | 30 + .../spec/functions/delete_regex_spec.rb | 54 + .../stdlib/spec/functions/delete_spec.rb | 76 + .../functions/delete_undef_values_spec.rb | 57 + .../spec/functions/delete_values_spec.rb | 41 + .../stdlib/spec/functions/deprecation_spec.rb | 60 + .../stdlib/spec/functions/difference_spec.rb | 23 + .../stdlib/spec/functions/dig44_spec.rb | 131 ++ .../modules/stdlib/spec/functions/dig_spec.rb | 13 + .../stdlib/spec/functions/dirname_spec.rb | 18 + .../stdlib/spec/functions/dos2unix_spec.rb | 47 + .../stdlib/spec/functions/downcase_spec.rb | 15 + .../stdlib/spec/functions/empty_spec.rb | 22 + .../spec/functions/ensure_packages_spec.rb | 51 + .../spec/functions/ensure_resource_spec.rb | 117 ++ .../stdlib/spec/functions/flatten_spec.rb | 15 + .../stdlib/spec/functions/floor_spec.rb | 12 + .../spec/functions/fqdn_rand_string_spec.rb | 66 + .../stdlib/spec/functions/fqdn_rotate_spec.rb | 76 + .../stdlib/spec/functions/fqdn_uuid_spec.rb | 13 + .../spec/functions/get_module_path_spec.rb | 48 + .../stdlib/spec/functions/getparam_spec.rb | 35 + .../stdlib/spec/functions/getvar_spec.rb | 38 + .../stdlib/spec/functions/grep_spec.rb | 20 + .../spec/functions/has_interface_with_spec.rb | 38 + .../spec/functions/has_ip_address_spec.rb | 22 + .../spec/functions/has_ip_network_spec.rb | 21 + .../stdlib/spec/functions/has_key_spec.rb | 20 + .../stdlib/spec/functions/hash_spec.rb | 15 + .../spec/functions/intersection_spec.rb | 22 + .../stdlib/spec/functions/is_a_spec.rb | 30 + .../stdlib/spec/functions/is_array_spec.rb | 36 + .../stdlib/spec/functions/is_bool_spec.rb | 33 + .../spec/functions/is_domain_name_spec.rb | 43 + .../spec/functions/is_email_address_spec.rb | 14 + .../stdlib/spec/functions/is_float_spec.rb | 42 + .../spec/functions/is_function_available.rb | 9 + .../stdlib/spec/functions/is_hash_spec.rb | 11 + .../stdlib/spec/functions/is_integer_spec.rb | 45 + .../spec/functions/is_ip_address_spec.rb | 40 + .../spec/functions/is_ipv4_address_spec.rb | 32 + .../spec/functions/is_ipv6_address_spec.rb | 30 + .../spec/functions/is_mac_address_spec.rb | 30 + .../stdlib/spec/functions/is_numeric_spec.rb | 48 + .../stdlib/spec/functions/is_string_spec.rb | 47 + .../functions/join_keys_to_values_spec.rb | 30 + .../stdlib/spec/functions/join_spec.rb | 20 + .../stdlib/spec/functions/keys_spec.rb | 24 + .../modules/stdlib/spec/functions/length.rb | 35 + .../functions/load_module_metadata_spec.rb | 61 + .../stdlib/spec/functions/loadjson_spec.rb | 66 + .../stdlib/spec/functions/loadyaml_spec.rb | 36 + .../stdlib/spec/functions/lstrip_spec.rb | 35 + .../modules/stdlib/spec/functions/max_spec.rb | 21 + .../stdlib/spec/functions/member_spec.rb | 23 + .../stdlib/spec/functions/merge_spec.rb | 25 + .../modules/stdlib/spec/functions/min_spec.rb | 21 + .../stdlib/spec/functions/num2bool_spec.rb | 22 + .../stdlib/spec/functions/parsejson_spec.rb | 69 + .../stdlib/spec/functions/parseyaml_spec.rb | 86 + .../spec/functions/pick_default_spec.rb | 26 + .../stdlib/spec/functions/pick_spec.rb | 17 + .../stdlib/spec/functions/prefix_spec.rb | 29 + .../stdlib/spec/functions/private_spec.rb | 56 + .../stdlib/spec/functions/pw_hash_spec.rb | 69 + .../stdlib/spec/functions/range_spec.rb | 126 ++ .../spec/functions/regexpescape_spec.rb | 41 + .../stdlib/spec/functions/reject_spec.rb | 20 + .../stdlib/spec/functions/reverse_spec.rb | 33 + .../stdlib/spec/functions/rstrip_spec.rb | 35 + .../stdlib/spec/functions/seeded_rand_spec.rb | 58 + .../spec/functions/shell_escape_spec.rb | 27 + .../stdlib/spec/functions/shell_join_spec.rb | 28 + .../stdlib/spec/functions/shell_split_spec.rb | 29 + .../stdlib/spec/functions/shuffle_spec.rb | 38 + .../stdlib/spec/functions/size_spec.rb | 38 + .../stdlib/spec/functions/sort_spec.rb | 24 + .../stdlib/spec/functions/squeeze_spec.rb | 50 + .../stdlib/spec/functions/str2bool_spec.rb | 23 + .../spec/functions/str2saltedsha512_spec.rb | 17 + .../stdlib/spec/functions/strftime_spec.rb | 26 + .../stdlib/spec/functions/strip_spec.rb | 35 + .../stdlib/spec/functions/suffix_spec.rb | 40 + .../stdlib/spec/functions/swapcase_spec.rb | 40 + .../stdlib/spec/functions/time_spec.rb | 21 + .../stdlib/spec/functions/to_bytes_spec.rb | 72 + .../spec/functions/try_get_value_spec.rb | 100 + .../stdlib/spec/functions/type3x_spec.rb | 41 + .../stdlib/spec/functions/type_of_spec.rb | 25 + .../stdlib/spec/functions/type_spec.rb | 42 + .../stdlib/spec/functions/union_spec.rb | 25 + .../stdlib/spec/functions/unique_spec.rb | 29 + .../stdlib/spec/functions/unix2dos_spec.rb | 40 + .../stdlib/spec/functions/upcase_spec.rb | 26 + .../stdlib/spec/functions/uriescape_spec.rb | 36 + .../functions/validate_absolute_path_spec.rb | 73 + .../spec/functions/validate_array_spec.rb | 37 + .../spec/functions/validate_augeas_spec.rb | 75 + .../spec/functions/validate_bool_spec.rb | 36 + .../spec/functions/validate_cmd_spec.rb | 35 + .../functions/validate_email_address_spec.rb | 23 + .../spec/functions/validate_hash_spec.rb | 38 + .../spec/functions/validate_integer_spec.rb | 101 + .../functions/validate_ip_address_spec.rb | 65 + .../functions/validate_ipv4_address_spec.rb | 41 + .../functions/validate_ipv6_address_spec.rb | 60 + .../spec/functions/validate_legacy_spec.rb | 68 + .../spec/functions/validate_numeric_spec.rb | 100 + .../stdlib/spec/functions/validate_re_spec.rb | 56 + .../spec/functions/validate_slength_spec.rb | 72 + .../spec/functions/validate_string_spec.rb | 33 + .../validate_x509_rsa_key_pair_spec.rb | 180 ++ .../stdlib/spec/functions/values_at_spec.rb | 54 + .../stdlib/spec/functions/values_spec.rb | 24 + .../modules/stdlib/spec/functions/zip_spec.rb | 21 + .../monkey_patches/alias_should_to_must.rb | 9 + .../spec/monkey_patches/publicize_methods.rb | 11 + .../prod/modules/stdlib/spec/spec_helper.rb | 8 + .../stdlib/spec/spec_helper_acceptance.rb | 55 + .../modules/stdlib/spec/spec_helper_local.rb | 31 + .../stdlib/spec/support/shared_data.rb | 38 + .../stdlib/spec/unit/ensure_resources_spec.rb | 22 + .../spec/unit/facter/facter_dot_d_spec.rb | 32 + .../spec/unit/facter/package_provider_spec.rb | 44 + .../spec/unit/facter/pe_version_spec.rb | 88 + .../stdlib/spec/unit/facter/root_home_spec.rb | 65 + .../spec/unit/facter/service_provider_spec.rb | 37 + .../unit/facter/util/puppet_settings_spec.rb | 37 + .../parser/functions/enclose_ipv6_spec.rb | 69 + .../parser/functions/is_absolute_path_spec.rb | 86 + .../puppet/provider/file_line/ruby_spec.rb | 447 ++++ .../spec/unit/puppet/type/anchor_spec.rb | 11 + .../spec/unit/puppet/type/file_line_spec.rb | 113 ++ .../prod/modules/stdlib/types/absolutepath.pp | 2 + .../stdlib/types/compat/absolute_path.pp | 7 + .../prod/modules/stdlib/types/compat/array.pp | 2 + .../prod/modules/stdlib/types/compat/bool.pp | 2 + .../prod/modules/stdlib/types/compat/float.pp | 19 + .../prod/modules/stdlib/types/compat/hash.pp | 2 + .../modules/stdlib/types/compat/integer.pp | 23 + .../modules/stdlib/types/compat/ip_address.pp | 1 + .../prod/modules/stdlib/types/compat/ipv4.pp | 2 + .../prod/modules/stdlib/types/compat/ipv6.pp | 1 + .../modules/stdlib/types/compat/numeric.pp | 23 + .../prod/modules/stdlib/types/compat/re.pp | 3 + .../modules/stdlib/types/compat/string.pp | 2 + .../prod/modules/stdlib/types/httpsurl.pp | 1 + .../prod/modules/stdlib/types/httpurl.pp | 1 + .../prod/modules/stdlib/types/unixpath.pp | 2 + .../prod/modules/stdlib/types/windowspath.pp | 1 + environments/production/environment.conf | 18 + environments/production/manifests/site.pp | 21 + .../production/modules/mysql/CHANGELOG.md | 770 +++++++ .../production/modules/mysql/CONTRIBUTING.md | 218 ++ environments/production/modules/mysql/Gemfile | 52 + environments/production/modules/mysql/LICENSE | 202 ++ environments/production/modules/mysql/NOTICE | 18 + .../production/modules/mysql/README.md | 1050 ++++++++++ .../production/modules/mysql/Rakefile | 38 + environments/production/modules/mysql/TODO | 8 + .../production/modules/mysql/checksums.json | 115 ++ .../modules/mysql/examples/backup.pp | 9 + .../modules/mysql/examples/bindings.pp | 3 + .../production/modules/mysql/examples/java.pp | 1 + .../modules/mysql/examples/mysql_database.pp | 17 + .../modules/mysql/examples/mysql_db.pp | 17 + .../modules/mysql/examples/mysql_grant.pp | 5 + .../modules/mysql/examples/mysql_plugin.pp | 23 + .../modules/mysql/examples/mysql_user.pp | 32 + .../production/modules/mysql/examples/perl.pp | 1 + .../modules/mysql/examples/python.pp | 1 + .../production/modules/mysql/examples/ruby.pp | 1 + .../modules/mysql/examples/server.pp | 3 + .../mysql/examples/server/account_security.pp | 4 + .../modules/mysql/examples/server/config.pp | 3 + .../mysql/lib/facter/mysql_server_id.rb | 9 + .../modules/mysql/lib/facter/mysql_version.rb | 8 + .../mysql/lib/facter/mysqld_version.rb | 5 + .../parser/functions/mysql_deepmerge.rb | 58 + .../puppet/parser/functions/mysql_dirname.rb | 15 + .../puppet/parser/functions/mysql_password.rb | 17 + .../parser/functions/mysql_strip_hash.rb | 21 + .../mysql/lib/puppet/provider/mysql.rb | 115 ++ .../puppet/provider/mysql_database/mysql.rb | 68 + .../puppet/provider/mysql_datadir/mysql.rb | 78 + .../lib/puppet/provider/mysql_grant/mysql.rb | 176 ++ .../lib/puppet/provider/mysql_plugin/mysql.rb | 53 + .../lib/puppet/provider/mysql_user/mysql.rb | 197 ++ .../mysql/lib/puppet/type/mysql_database.rb | 25 + .../mysql/lib/puppet/type/mysql_datadir.rb | 35 + .../mysql/lib/puppet/type/mysql_grant.rb | 103 + .../mysql/lib/puppet/type/mysql_plugin.rb | 17 + .../mysql/lib/puppet/type/mysql_user.rb | 105 + .../mysql/manifests/backup/mysqlbackup.pp | 106 + .../mysql/manifests/backup/mysqldump.pp | 73 + .../mysql/manifests/backup/xtrabackup.pp | 67 + .../modules/mysql/manifests/bindings.pp | 57 + .../mysql/manifests/bindings/client_dev.pp | 15 + .../mysql/manifests/bindings/daemon_dev.pp | 15 + .../modules/mysql/manifests/bindings/java.pp | 11 + .../modules/mysql/manifests/bindings/perl.pp | 11 + .../modules/mysql/manifests/bindings/php.pp | 11 + .../mysql/manifests/bindings/python.pp | 11 + .../modules/mysql/manifests/bindings/ruby.pp | 11 + .../modules/mysql/manifests/client.pp | 29 + .../modules/mysql/manifests/client/install.pp | 14 + .../production/modules/mysql/manifests/db.pp | 72 + .../modules/mysql/manifests/params.pp | 450 +++++ .../modules/mysql/manifests/server.pp | 89 + .../manifests/server/account_security.pp | 39 + .../modules/mysql/manifests/server/backup.pp | 55 + .../mysql/manifests/server/binarylog.pp | 22 + .../modules/mysql/manifests/server/config.pp | 56 + .../modules/mysql/manifests/server/install.pp | 13 + .../mysql/manifests/server/installdb.pp | 46 + .../modules/mysql/manifests/server/monitor.pp | 24 + .../mysql/manifests/server/mysqltuner.pp | 52 + .../mysql/manifests/server/providers.pp | 8 + .../mysql/manifests/server/root_password.pp | 46 + .../modules/mysql/manifests/server/service.pp | 58 + .../production/modules/mysql/metadata.json | 80 + .../spec/acceptance/mysql_backup_spec.rb | 205 ++ .../mysql/spec/acceptance/mysql_db_spec.rb | 65 + .../mysql/spec/acceptance/mysql_helper.rb | 8 + .../spec/acceptance/mysql_server_spec.rb | 107 + .../spec/acceptance/nodesets/centos-7-x64.yml | 10 + .../spec/acceptance/nodesets/debian-8-x64.yml | 10 + .../spec/acceptance/nodesets/default.yml | 10 + .../acceptance/nodesets/docker/centos-7.yml | 12 + .../acceptance/nodesets/docker/debian-8.yml | 11 + .../nodesets/docker/ubuntu-14.04.yml | 12 + .../acceptance/types/mysql_database_spec.rb | 64 + .../spec/acceptance/types/mysql_grant_spec.rb | 578 ++++++ .../acceptance/types/mysql_plugin_spec.rb | 76 + .../spec/acceptance/types/mysql_user_spec.rb | 146 ++ .../spec/classes/graceful_failures_spec.rb | 19 + .../mysql/spec/classes/mycnf_template_spec.rb | 86 + .../mysql/spec/classes/mysql_bindings_spec.rb | 32 + .../mysql/spec/classes/mysql_client_spec.rb | 38 + .../mysql_server_account_security_spec.rb | 86 + .../spec/classes/mysql_server_backup_spec.rb | 407 ++++ .../spec/classes/mysql_server_monitor_spec.rb | 38 + .../classes/mysql_server_mysqltuner_spec.rb | 46 + .../mysql/spec/classes/mysql_server_spec.rb | 220 ++ .../mysql/spec/defines/mysql_db_spec.rb | 83 + .../production/modules/mysql/spec/spec.opts | 6 + .../modules/mysql/spec/spec_helper.rb | 8 + .../mysql/spec/spec_helper_acceptance.rb | 66 + .../modules/mysql/spec/spec_helper_local.rb | 3 + .../spec/unit/facter/mysql_server_id_spec.rb | 27 + .../spec/unit/facter/mysql_version_spec.rb | 20 + .../spec/unit/facter/mysqld_version_spec.rb | 20 + .../puppet/functions/mysql_deepmerge_spec.rb | 91 + .../puppet/functions/mysql_password_spec.rb | 37 + .../provider/mysql_database/mysql_spec.rb | 118 ++ .../provider/mysql_plugin/mysql_spec.rb | 71 + .../puppet/provider/mysql_user/mysql_spec.rb | 345 ++++ .../unit/puppet/type/mysql_database_spec.rb | 29 + .../spec/unit/puppet/type/mysql_grant_spec.rb | 74 + .../unit/puppet/type/mysql_plugin_spec.rb | 24 + .../spec/unit/puppet/type/mysql_user_spec.rb | 136 ++ .../modules/mysql/templates/meb.cnf.erb | 18 + .../modules/mysql/templates/my.cnf.erb | 24 + .../modules/mysql/templates/my.cnf.pass.erb | 11 + .../mysql/templates/mysqlbackup.sh.erb | 104 + .../modules/mysql/templates/xtrabackup.sh.erb | 21 + .../production/modules/nginx/ChangeLog | 53 + environments/production/modules/nginx/LICENSE | 11 + .../production/modules/nginx/Modulefile | 10 + environments/production/modules/nginx/README | 0 .../production/modules/nginx/README.markdown | 20 + .../modules/nginx/manifests/config.pp | 77 + .../modules/nginx/manifests/init.pp | 72 + .../modules/nginx/manifests/package.pp | 40 + .../modules/nginx/manifests/package/debian.pp | 20 + .../modules/nginx/manifests/package/redhat.pp | 42 + .../modules/nginx/manifests/package/suse.pp | 29 + .../modules/nginx/manifests/params.pp | 64 + .../nginx/manifests/resource/location.pp | 117 ++ .../nginx/manifests/resource/upstream.pp | 40 + .../modules/nginx/manifests/resource/vhost.pp | 158 ++ .../modules/nginx/manifests/service.pp | 38 + .../production/modules/nginx/metadata.json | 52 + .../nginx/templates/conf.d/nginx.conf.erb | 33 + .../nginx/templates/conf.d/proxy.conf.erb | 10 + .../nginx/templates/conf.d/upstream.erb | 5 + .../nginx/templates/vhost/vhost_footer.erb | 9 + .../nginx/templates/vhost/vhost_header.erb | 8 + .../templates/vhost/vhost_location_alias.erb | 9 + .../vhost/vhost_location_directory.erb | 13 + .../templates/vhost/vhost_location_proxy.erb | 10 + .../vhost/vhost_location_stub_status.erb | 9 + .../templates/vhost/vhost_ssl_header.erb | 16 + .../production/modules/nginx/tests/init.pp | 9 + .../modules/nginx/tests/location_alias.pp | 8 + .../modules/nginx/tests/upstream.pp | 10 + .../production/modules/nginx/tests/vhost.pp | 16 + .../modules/nginx/tests/vhost_ssl.erb | 17 + .../production/modules/staging/CHANGELOG.md | 30 + .../modules/staging/CONTRIBUTING.md | 97 + .../production/modules/staging/Gemfile | 68 + .../production/modules/staging/LICENSE | 11 + .../production/modules/staging/README.md | 79 + .../production/modules/staging/Rakefile | 33 + .../production/modules/staging/checksums.json | 60 + .../modules/staging/docs/deploy.html | 113 ++ .../modules/staging/docs/extract.html | 176 ++ .../production/modules/staging/docs/file.html | 178 ++ .../production/modules/staging/docs/init.html | 87 + .../modules/staging/examples/deploy.pp | 4 + .../modules/staging/examples/extract.pp | 25 + .../modules/staging/examples/file.pp | 17 + .../modules/staging/examples/init.pp | 1 + .../staging/examples/scope_defaults.pp | 7 + .../modules/staging/examples/staging_parse.pp | 12 + .../production/modules/staging/files/sample | 1 + .../modules/staging/files/sample.tar.bz2 | Bin 0 -> 27984 bytes .../modules/staging/files/sample.tar.gz | Bin 0 -> 33352 bytes .../staging/lib/facter/staging_http_get.rb | 27 + .../staging/lib/facter/staging_windir.rb | 11 + .../puppet/parser/functions/scope_defaults.rb | 18 + .../puppet/parser/functions/staging_parse.rb | 39 + .../modules/staging/manifests/deploy.pp | 59 + .../modules/staging/manifests/extract.pp | 126 ++ .../modules/staging/manifests/file.pp | 166 ++ .../modules/staging/manifests/init.pp | 34 + .../modules/staging/manifests/params.pp | 40 + .../production/modules/staging/metadata.json | 115 ++ .../acceptance/nodesets/centos-511-x64.yml | 15 + .../acceptance/nodesets/centos-66-x64-pe.yml | 17 + .../acceptance/nodesets/centos-66-x64.yml | 15 + .../acceptance/nodesets/centos-72-x64.yml | 15 + .../acceptance/nodesets/debian-78-x64.yml | 15 + .../acceptance/nodesets/debian-82-x64.yml | 15 + .../acceptance/nodesets/docker/centos-5.yml | 19 + .../acceptance/nodesets/docker/centos-6.yml | 20 + .../acceptance/nodesets/docker/centos-7.yml | 18 + .../acceptance/nodesets/docker/debian-7.yml | 19 + .../acceptance/nodesets/docker/debian-8.yml | 20 + .../nodesets/docker/ubuntu-12.04.yml | 19 + .../nodesets/docker/ubuntu-14.04.yml | 21 + .../nodesets/docker/ubuntu-16.04.yml | 19 + .../acceptance/nodesets/fedora-24-x64.yml | 15 + .../acceptance/nodesets/fedora-25-x64.yml | 18 + .../nodesets/ubuntu-server-1204-x64.yml | 15 + .../nodesets/ubuntu-server-1404-x64.yml | 15 + .../nodesets/ubuntu-server-1604-x64.yml | 15 + .../staging/spec/classes/coverage_spec.rb | 4 + .../modules/staging/spec/default_facts.yml | 14 + .../spec/defines/staging_deploy_spec.rb | 104 + .../spec/defines/staging_extract_spec.rb | 175 ++ .../staging/spec/defines/staging_file_spec.rb | 219 ++ .../modules/staging/spec/fixtures/hiera.yaml | 9 + .../production/modules/staging/spec/spec.opts | 6 + .../modules/staging/spec/spec_helper.rb | 32 + .../parser/functions/scope_defaults_spec.rb | 45 + .../parser/functions/staging_parse_spec.rb | 51 + .../production/modules/stdlib/CHANGELOG.md | 775 +++++++ .../production/modules/stdlib/CONTRIBUTING.md | 217 ++ .../production/modules/stdlib/Gemfile | 86 + .../production/modules/stdlib/LICENSE | 202 ++ .../production/modules/stdlib/MAINTAINERS.md | 6 + environments/production/modules/stdlib/NOTICE | 23 + .../production/modules/stdlib/README.markdown | 1791 +++++++++++++++++ .../modules/stdlib/README_DEVELOPER.markdown | 35 + .../modules/stdlib/README_SPECS.markdown | 7 + .../modules/stdlib/RELEASE_PROCESS.markdown | 24 + .../production/modules/stdlib/Rakefile | 37 + .../production/modules/stdlib/appveyor.yml | 40 + .../production/modules/stdlib/checksums.json | 508 +++++ .../modules/stdlib/examples/file_line.pp | 9 + .../stdlib/examples/has_interface_with.pp | 9 + .../modules/stdlib/examples/has_ip_address.pp | 3 + .../modules/stdlib/examples/has_ip_network.pp | 3 + .../modules/stdlib/examples/init.pp | 1 + .../modules/stdlib/lib/facter/facter_dot_d.rb | 202 ++ .../stdlib/lib/facter/package_provider.rb | 21 + .../modules/stdlib/lib/facter/pe_version.rb | 58 + .../stdlib/lib/facter/puppet_settings.rb | 43 + .../modules/stdlib/lib/facter/root_home.rb | 45 + .../stdlib/lib/facter/service_provider.rb | 17 + .../stdlib/lib/facter/util/puppet_settings.rb | 21 + .../lib/puppet/functions/deprecation.rb | 29 + .../stdlib/lib/puppet/functions/is_a.rb | 32 + .../lib/puppet/functions/is_absolute_path.rb | 15 + .../stdlib/lib/puppet/functions/is_array.rb | 15 + .../stdlib/lib/puppet/functions/is_bool.rb | 15 + .../stdlib/lib/puppet/functions/is_float.rb | 15 + .../lib/puppet/functions/is_ip_address.rb | 15 + .../lib/puppet/functions/is_ipv4_address.rb | 15 + .../lib/puppet/functions/is_ipv6_address.rb | 15 + .../stdlib/lib/puppet/functions/is_numeric.rb | 15 + .../stdlib/lib/puppet/functions/is_string.rb | 15 + .../stdlib/lib/puppet/functions/length.rb | 14 + .../stdlib/lib/puppet/functions/type_of.rb | 19 + .../functions/validate_absolute_path.rb | 15 + .../lib/puppet/functions/validate_array.rb | 15 + .../lib/puppet/functions/validate_bool.rb | 15 + .../lib/puppet/functions/validate_hash.rb | 15 + .../lib/puppet/functions/validate_integer.rb | 15 + .../puppet/functions/validate_ip_address.rb | 15 + .../puppet/functions/validate_ipv4_address.rb | 15 + .../puppet/functions/validate_ipv6_address.rb | 15 + .../lib/puppet/functions/validate_legacy.rb | 62 + .../lib/puppet/functions/validate_numeric.rb | 15 + .../lib/puppet/functions/validate_re.rb | 15 + .../lib/puppet/functions/validate_slength.rb | 15 + .../lib/puppet/functions/validate_string.rb | 15 + .../stdlib/lib/puppet/parser/functions/abs.rb | 34 + .../lib/puppet/parser/functions/any2array.rb | 33 + .../lib/puppet/parser/functions/any2bool.rb | 54 + .../puppet/parser/functions/assert_private.rb | 28 + .../lib/puppet/parser/functions/base64.rb | 71 + .../lib/puppet/parser/functions/basename.rb | 34 + .../lib/puppet/parser/functions/bool2num.rb | 25 + .../lib/puppet/parser/functions/bool2str.rb | 44 + .../lib/puppet/parser/functions/camelcase.rb | 32 + .../lib/puppet/parser/functions/capitalize.rb | 30 + .../lib/puppet/parser/functions/ceiling.rb | 22 + .../lib/puppet/parser/functions/chomp.rb | 32 + .../lib/puppet/parser/functions/chop.rb | 34 + .../lib/puppet/parser/functions/clamp.rb | 29 + .../lib/puppet/parser/functions/concat.rb | 40 + .../puppet/parser/functions/convert_base.rb | 35 + .../lib/puppet/parser/functions/count.rb | 21 + .../lib/puppet/parser/functions/deep_merge.rb | 44 + .../parser/functions/defined_with_params.rb | 38 + .../lib/puppet/parser/functions/delete.rb | 43 + .../lib/puppet/parser/functions/delete_at.rb | 46 + .../puppet/parser/functions/delete_regex.rb | 44 + .../parser/functions/delete_undef_values.rb | 31 + .../puppet/parser/functions/delete_values.rb | 23 + .../puppet/parser/functions/deprecation.rb | 16 + .../lib/puppet/parser/functions/difference.rb | 35 + .../stdlib/lib/puppet/parser/functions/dig.rb | 16 + .../lib/puppet/parser/functions/dig44.rb | 70 + .../lib/puppet/parser/functions/dirname.rb | 21 + .../lib/puppet/parser/functions/dos2unix.rb | 15 + .../lib/puppet/parser/functions/downcase.rb | 31 + .../lib/puppet/parser/functions/empty.rb | 29 + .../puppet/parser/functions/enclose_ipv6.rb | 42 + .../parser/functions/ensure_packages.rb | 47 + .../parser/functions/ensure_resource.rb | 46 + .../parser/functions/ensure_resources.rb | 54 + .../lib/puppet/parser/functions/flatten.rb | 32 + .../lib/puppet/parser/functions/floor.rb | 22 + .../parser/functions/fqdn_rand_string.rb | 34 + .../puppet/parser/functions/fqdn_rotate.rb | 61 + .../lib/puppet/parser/functions/fqdn_uuid.rb | 92 + .../parser/functions/get_module_path.rb | 17 + .../lib/puppet/parser/functions/getparam.rb | 35 + .../lib/puppet/parser/functions/getvar.rb | 35 + .../lib/puppet/parser/functions/grep.rb | 32 + .../parser/functions/has_interface_with.rb | 70 + .../puppet/parser/functions/has_ip_address.rb | 24 + .../puppet/parser/functions/has_ip_network.rb | 24 + .../lib/puppet/parser/functions/has_key.rb | 28 + .../lib/puppet/parser/functions/hash.rb | 39 + .../puppet/parser/functions/intersection.rb | 33 + .../parser/functions/is_absolute_path.rb | 50 + .../lib/puppet/parser/functions/is_array.rb | 21 + .../lib/puppet/parser/functions/is_bool.rb | 21 + .../puppet/parser/functions/is_domain_name.rb | 53 + .../parser/functions/is_email_address.rb | 20 + .../lib/puppet/parser/functions/is_float.rb | 31 + .../parser/functions/is_function_available.rb | 25 + .../lib/puppet/parser/functions/is_hash.rb | 21 + .../lib/puppet/parser/functions/is_integer.rb | 46 + .../puppet/parser/functions/is_ip_address.rb | 33 + .../parser/functions/is_ipv4_address.rb | 29 + .../parser/functions/is_ipv6_address.rb | 29 + .../puppet/parser/functions/is_mac_address.rb | 26 + .../lib/puppet/parser/functions/is_numeric.rb | 74 + .../lib/puppet/parser/functions/is_string.rb | 28 + .../lib/puppet/parser/functions/join.rb | 40 + .../parser/functions/join_keys_to_values.rb | 53 + .../lib/puppet/parser/functions/keys.rb | 25 + .../parser/functions/load_module_metadata.rb | 24 + .../lib/puppet/parser/functions/loadjson.rb | 34 + .../lib/puppet/parser/functions/loadyaml.rb | 34 + .../lib/puppet/parser/functions/lstrip.rb | 30 + .../stdlib/lib/puppet/parser/functions/max.rb | 20 + .../lib/puppet/parser/functions/member.rb | 60 + .../lib/puppet/parser/functions/merge.rb | 34 + .../stdlib/lib/puppet/parser/functions/min.rb | 20 + .../lib/puppet/parser/functions/num2bool.rb | 42 + .../lib/puppet/parser/functions/parsejson.rb | 29 + .../lib/puppet/parser/functions/parseyaml.rb | 33 + .../lib/puppet/parser/functions/pick.rb | 29 + .../puppet/parser/functions/pick_default.rb | 35 + .../lib/puppet/parser/functions/prefix.rb | 51 + .../lib/puppet/parser/functions/private.rb | 17 + .../stdlib/lib/puppet/parser/functions/pry.rb | 30 + .../lib/puppet/parser/functions/pw_hash.rb | 59 + .../lib/puppet/parser/functions/range.rb | 85 + .../puppet/parser/functions/regexpescape.rb | 29 + .../lib/puppet/parser/functions/reject.rb | 31 + .../lib/puppet/parser/functions/reverse.rb | 25 + .../lib/puppet/parser/functions/rstrip.rb | 29 + .../puppet/parser/functions/seeded_rand.rb | 22 + .../puppet/parser/functions/shell_escape.rb | 29 + .../lib/puppet/parser/functions/shell_join.rb | 30 + .../puppet/parser/functions/shell_split.rb | 25 + .../lib/puppet/parser/functions/shuffle.rb | 43 + .../lib/puppet/parser/functions/size.rb | 46 + .../lib/puppet/parser/functions/sort.rb | 27 + .../lib/puppet/parser/functions/squeeze.rb | 35 + .../lib/puppet/parser/functions/str2bool.rb | 44 + .../parser/functions/str2saltedsha512.rb | 31 + .../lib/puppet/parser/functions/strftime.rb | 106 + .../lib/puppet/parser/functions/strip.rb | 36 + .../lib/puppet/parser/functions/suffix.rb | 52 + .../lib/puppet/parser/functions/swapcase.rb | 37 + .../lib/puppet/parser/functions/time.rb | 49 + .../lib/puppet/parser/functions/to_bytes.rb | 30 + .../puppet/parser/functions/try_get_value.rb | 53 + .../lib/puppet/parser/functions/type.rb | 19 + .../lib/puppet/parser/functions/type3x.rb | 50 + .../lib/puppet/parser/functions/union.rb | 28 + .../lib/puppet/parser/functions/unique.rb | 48 + .../lib/puppet/parser/functions/unix2dos.rb | 15 + .../lib/puppet/parser/functions/upcase.rb | 44 + .../lib/puppet/parser/functions/uriescape.rb | 33 + .../functions/validate_absolute_path.rb | 54 + .../puppet/parser/functions/validate_array.rb | 35 + .../parser/functions/validate_augeas.rb | 83 + .../puppet/parser/functions/validate_bool.rb | 37 + .../puppet/parser/functions/validate_cmd.rb | 63 + .../functions/validate_email_address.rb | 31 + .../puppet/parser/functions/validate_hash.rb | 35 + .../parser/functions/validate_integer.rb | 134 ++ .../parser/functions/validate_ip_address.rb | 52 + .../parser/functions/validate_ipv4_address.rb | 50 + .../parser/functions/validate_ipv6_address.rb | 51 + .../parser/functions/validate_numeric.rb | 96 + .../puppet/parser/functions/validate_re.rb | 50 + .../parser/functions/validate_slength.rb | 71 + .../parser/functions/validate_string.rb | 41 + .../functions/validate_x509_rsa_key_pair.rb | 47 + .../lib/puppet/parser/functions/values.rb | 38 + .../lib/puppet/parser/functions/values_at.rb | 93 + .../stdlib/lib/puppet/parser/functions/zip.rb | 38 + .../lib/puppet/provider/file_line/ruby.rb | 132 ++ .../modules/stdlib/lib/puppet/type/anchor.rb | 46 + .../stdlib/lib/puppet/type/file_line.rb | 146 ++ .../modules/stdlib/manifests/init.pp | 18 + .../modules/stdlib/manifests/stages.pp | 43 + .../production/modules/stdlib/metadata.json | 112 ++ .../stdlib/spec/acceptance/abs_spec.rb | 30 + .../stdlib/spec/acceptance/anchor_spec.rb | 26 + .../stdlib/spec/acceptance/any2array_spec.rb | 49 + .../stdlib/spec/acceptance/base64_spec.rb | 18 + .../stdlib/spec/acceptance/bool2num_spec.rb | 34 + .../stdlib/spec/acceptance/build_csv.rb | 83 + .../stdlib/spec/acceptance/capitalize_spec.rb | 33 + .../stdlib/spec/acceptance/ceiling_spec.rb | 39 + .../stdlib/spec/acceptance/chomp_spec.rb | 21 + .../stdlib/spec/acceptance/chop_spec.rb | 45 + .../stdlib/spec/acceptance/clamp_spec.rb | 40 + .../stdlib/spec/acceptance/concat_spec.rb | 54 + .../stdlib/spec/acceptance/count_spec.rb | 30 + .../stdlib/spec/acceptance/deep_merge_spec.rb | 20 + .../acceptance/defined_with_params_spec.rb | 22 + .../stdlib/spec/acceptance/delete_at_spec.rb | 19 + .../stdlib/spec/acceptance/delete_spec.rb | 19 + .../acceptance/delete_undef_values_spec.rb | 19 + .../spec/acceptance/delete_values_spec.rb | 25 + .../spec/acceptance/deprecation_spec.rb | 102 + .../stdlib/spec/acceptance/difference_spec.rb | 26 + .../stdlib/spec/acceptance/dirname_spec.rb | 42 + .../stdlib/spec/acceptance/downcase_spec.rb | 39 + .../stdlib/spec/acceptance/empty_spec.rb | 53 + .../spec/acceptance/ensure_resource_spec.rb | 30 + .../stdlib/spec/acceptance/flatten_spec.rb | 39 + .../stdlib/spec/acceptance/floor_spec.rb | 39 + .../spec/acceptance/fqdn_rand_string_spec.rb | 66 + .../spec/acceptance/fqdn_rotate_spec.rb | 64 + .../spec/acceptance/get_module_path_spec.rb | 27 + .../stdlib/spec/acceptance/getparam_spec.rb | 24 + .../stdlib/spec/acceptance/getvar_spec.rb | 26 + .../stdlib/spec/acceptance/grep_spec.rb | 26 + .../acceptance/has_interface_with_spec.rb | 54 + .../spec/acceptance/has_ip_address_spec.rb | 33 + .../spec/acceptance/has_ip_network_spec.rb | 33 + .../stdlib/spec/acceptance/has_key_spec.rb | 41 + .../stdlib/spec/acceptance/hash_spec.rb | 26 + .../spec/acceptance/intersection_spec.rb | 27 + .../stdlib/spec/acceptance/is_a_spec.rb | 30 + .../stdlib/spec/acceptance/is_array_spec.rb | 67 + .../stdlib/spec/acceptance/is_bool_spec.rb | 81 + .../spec/acceptance/is_domain_name_spec.rb | 83 + .../stdlib/spec/acceptance/is_float_spec.rb | 86 + .../acceptance/is_function_available_spec.rb | 58 + .../stdlib/spec/acceptance/is_hash_spec.rb | 63 + .../stdlib/spec/acceptance/is_integer_spec.rb | 95 + .../spec/acceptance/is_ip_address_spec.rb | 80 + .../spec/acceptance/is_ipv4_address_spec.rb | 52 + .../spec/acceptance/is_ipv6_address_spec.rb | 66 + .../spec/acceptance/is_mac_address_spec.rb | 38 + .../stdlib/spec/acceptance/is_numeric_spec.rb | 95 + .../stdlib/spec/acceptance/is_string_spec.rb | 113 ++ .../acceptance/join_keys_to_values_spec.rb | 24 + .../stdlib/spec/acceptance/join_spec.rb | 26 + .../stdlib/spec/acceptance/keys_spec.rb | 23 + .../stdlib/spec/acceptance/loadjson_spec.rb | 52 + .../stdlib/spec/acceptance/loadyaml_spec.rb | 56 + .../stdlib/spec/acceptance/lstrip_spec.rb | 34 + .../stdlib/spec/acceptance/max_spec.rb | 20 + .../stdlib/spec/acceptance/member_spec.rb | 54 + .../stdlib/spec/acceptance/merge_spec.rb | 23 + .../stdlib/spec/acceptance/min_spec.rb | 20 + .../spec/acceptance/nodesets/centos-7-x64.yml | 10 + .../spec/acceptance/nodesets/debian-8-x64.yml | 10 + .../spec/acceptance/nodesets/default.yml | 10 + .../acceptance/nodesets/docker/centos-7.yml | 12 + .../acceptance/nodesets/docker/debian-8.yml | 11 + .../nodesets/docker/ubuntu-14.04.yml | 12 + .../stdlib/spec/acceptance/num2bool_spec.rb | 76 + .../stdlib/spec/acceptance/parsejson_spec.rb | 55 + .../stdlib/spec/acceptance/parseyaml_spec.rb | 58 + .../spec/acceptance/pick_default_spec.rb | 54 + .../stdlib/spec/acceptance/pick_spec.rb | 44 + .../stdlib/spec/acceptance/prefix_spec.rb | 42 + .../stdlib/spec/acceptance/pw_hash_spec.rb | 34 + .../stdlib/spec/acceptance/range_spec.rb | 36 + .../stdlib/spec/acceptance/reject_spec.rb | 42 + .../stdlib/spec/acceptance/reverse_spec.rb | 23 + .../stdlib/spec/acceptance/rstrip_spec.rb | 34 + .../stdlib/spec/acceptance/shuffle_spec.rb | 34 + .../stdlib/spec/acceptance/size_spec.rb | 55 + .../stdlib/spec/acceptance/sort_spec.rb | 34 + .../stdlib/spec/acceptance/squeeze_spec.rb | 47 + .../stdlib/spec/acceptance/str2bool_spec.rb | 31 + .../spec/acceptance/str2saltedsha512_spec.rb | 22 + .../stdlib/spec/acceptance/strftime_spec.rb | 22 + .../stdlib/spec/acceptance/strip_spec.rb | 34 + .../stdlib/spec/acceptance/suffix_spec.rb | 42 + .../stdlib/spec/acceptance/swapcase_spec.rb | 22 + .../stdlib/spec/acceptance/time_spec.rb | 36 + .../stdlib/spec/acceptance/to_bytes_spec.rb | 27 + .../spec/acceptance/try_get_value_spec.rb | 47 + .../stdlib/spec/acceptance/type_spec.rb | 37 + .../stdlib/spec/acceptance/union_spec.rb | 25 + .../stdlib/spec/acceptance/unique_spec.rb | 33 + .../stdlib/spec/acceptance/upcase_spec.rb | 33 + .../stdlib/spec/acceptance/uriescape_spec.rb | 23 + .../acceptance/validate_absolute_path_spec.rb | 31 + .../spec/acceptance/validate_array_spec.rb | 37 + .../spec/acceptance/validate_augeas_spec.rb | 63 + .../spec/acceptance/validate_bool_spec.rb | 37 + .../spec/acceptance/validate_cmd_spec.rb | 52 + .../spec/acceptance/validate_hash_spec.rb | 37 + .../acceptance/validate_ipv4_address_spec.rb | 31 + .../acceptance/validate_ipv6_address_spec.rb | 31 + .../spec/acceptance/validate_re_spec.rb | 47 + .../spec/acceptance/validate_slength_spec.rb | 72 + .../spec/acceptance/validate_string_spec.rb | 43 + .../stdlib/spec/acceptance/values_at_spec.rb | 73 + .../stdlib/spec/acceptance/values_spec.rb | 30 + .../stdlib/spec/acceptance/zip_spec.rb | 67 + .../stdlib/spec/aliases/absolute_path_spec.rb | 67 + .../stdlib/spec/aliases/absolutepath_spec.rb | 50 + .../modules/stdlib/spec/aliases/array_spec.rb | 34 + .../modules/stdlib/spec/aliases/bool_spec.rb | 32 + .../modules/stdlib/spec/aliases/float_spec.rb | 28 + .../modules/stdlib/spec/aliases/hash_spec.rb | 32 + .../stdlib/spec/aliases/httpsurl_spec.rb | 44 + .../stdlib/spec/aliases/httpurl_spec.rb | 47 + .../stdlib/spec/aliases/integer_spec.rb | 28 + .../modules/stdlib/spec/aliases/ip_address.rb | 34 + .../modules/stdlib/spec/aliases/ipv4_spec.rb | 22 + .../modules/stdlib/spec/aliases/ipv6_spec.rb | 40 + .../stdlib/spec/aliases/numeric_spec.rb | 32 + .../stdlib/spec/aliases/string_spec.rb | 32 + .../stdlib/spec/aliases/unixpath_spec.rb | 47 + .../stdlib/spec/aliases/windowspath_spec.rb | 47 + .../stdlib/spec/fixtures/dscacheutil/root | 8 + .../modules/stdlib/spec/fixtures/lsuser/root | 2 + .../fixtures/test/manifests/absolute_path.pp | 6 + .../fixtures/test/manifests/absolutepath.pp | 6 + .../spec/fixtures/test/manifests/array.pp | 8 + .../spec/fixtures/test/manifests/bool.pp | 8 + .../test/manifests/ensure_resources.pp | 4 + .../spec/fixtures/test/manifests/float.pp | 8 + .../spec/fixtures/test/manifests/hash.pp | 8 + .../spec/fixtures/test/manifests/httpsurl.pp | 6 + .../spec/fixtures/test/manifests/httpurl.pp | 6 + .../spec/fixtures/test/manifests/integer.pp | 8 + .../fixtures/test/manifests/ip_address.pp | 6 + .../spec/fixtures/test/manifests/ipv4.pp | 6 + .../spec/fixtures/test/manifests/ipv6.pp | 6 + .../spec/fixtures/test/manifests/numeric.pp | 8 + .../spec/fixtures/test/manifests/string.pp | 8 + .../spec/fixtures/test/manifests/unixpath.pp | 6 + .../fixtures/test/manifests/windowspath.pp | 6 + .../modules/stdlib/spec/functions/abs_spec.rb | 30 + .../stdlib/spec/functions/any2array_spec.rb | 21 + .../stdlib/spec/functions/any2bool_spec.rb | 42 + .../spec/functions/assert_private_spec.rb | 41 + .../stdlib/spec/functions/base64_spec.rb | 37 + .../stdlib/spec/functions/basename_spec.rb | 19 + .../stdlib/spec/functions/bool2num_spec.rb | 14 + .../stdlib/spec/functions/bool2str_spec.rb | 17 + .../stdlib/spec/functions/camelcase_spec.rb | 17 + .../stdlib/spec/functions/capitalize_spec.rb | 15 + .../stdlib/spec/functions/ceiling_spec.rb | 13 + .../stdlib/spec/functions/chomp_spec.rb | 25 + .../stdlib/spec/functions/chop_spec.rb | 25 + .../stdlib/spec/functions/clamp_spec.rb | 16 + .../stdlib/spec/functions/concat_spec.rb | 29 + .../spec/functions/convert_base_spec.rb | 24 + .../stdlib/spec/functions/count_spec.rb | 23 + .../stdlib/spec/functions/deep_merge_spec.rb | 59 + .../functions/defined_with_params_spec.rb | 40 + .../stdlib/spec/functions/delete_at_spec.rb | 30 + .../spec/functions/delete_regex_spec.rb | 54 + .../stdlib/spec/functions/delete_spec.rb | 76 + .../functions/delete_undef_values_spec.rb | 57 + .../spec/functions/delete_values_spec.rb | 41 + .../stdlib/spec/functions/deprecation_spec.rb | 60 + .../stdlib/spec/functions/difference_spec.rb | 23 + .../stdlib/spec/functions/dig44_spec.rb | 131 ++ .../modules/stdlib/spec/functions/dig_spec.rb | 13 + .../stdlib/spec/functions/dirname_spec.rb | 18 + .../stdlib/spec/functions/dos2unix_spec.rb | 47 + .../stdlib/spec/functions/downcase_spec.rb | 15 + .../stdlib/spec/functions/empty_spec.rb | 22 + .../spec/functions/ensure_packages_spec.rb | 51 + .../spec/functions/ensure_resource_spec.rb | 117 ++ .../stdlib/spec/functions/flatten_spec.rb | 15 + .../stdlib/spec/functions/floor_spec.rb | 12 + .../spec/functions/fqdn_rand_string_spec.rb | 66 + .../stdlib/spec/functions/fqdn_rotate_spec.rb | 76 + .../stdlib/spec/functions/fqdn_uuid_spec.rb | 13 + .../spec/functions/get_module_path_spec.rb | 48 + .../stdlib/spec/functions/getparam_spec.rb | 35 + .../stdlib/spec/functions/getvar_spec.rb | 38 + .../stdlib/spec/functions/grep_spec.rb | 20 + .../spec/functions/has_interface_with_spec.rb | 38 + .../spec/functions/has_ip_address_spec.rb | 22 + .../spec/functions/has_ip_network_spec.rb | 21 + .../stdlib/spec/functions/has_key_spec.rb | 20 + .../stdlib/spec/functions/hash_spec.rb | 15 + .../spec/functions/intersection_spec.rb | 22 + .../stdlib/spec/functions/is_a_spec.rb | 30 + .../stdlib/spec/functions/is_array_spec.rb | 36 + .../stdlib/spec/functions/is_bool_spec.rb | 33 + .../spec/functions/is_domain_name_spec.rb | 43 + .../spec/functions/is_email_address_spec.rb | 14 + .../stdlib/spec/functions/is_float_spec.rb | 42 + .../spec/functions/is_function_available.rb | 9 + .../stdlib/spec/functions/is_hash_spec.rb | 11 + .../stdlib/spec/functions/is_integer_spec.rb | 45 + .../spec/functions/is_ip_address_spec.rb | 40 + .../spec/functions/is_ipv4_address_spec.rb | 32 + .../spec/functions/is_ipv6_address_spec.rb | 30 + .../spec/functions/is_mac_address_spec.rb | 30 + .../stdlib/spec/functions/is_numeric_spec.rb | 48 + .../stdlib/spec/functions/is_string_spec.rb | 47 + .../functions/join_keys_to_values_spec.rb | 30 + .../stdlib/spec/functions/join_spec.rb | 20 + .../stdlib/spec/functions/keys_spec.rb | 24 + .../modules/stdlib/spec/functions/length.rb | 35 + .../functions/load_module_metadata_spec.rb | 61 + .../stdlib/spec/functions/loadjson_spec.rb | 66 + .../stdlib/spec/functions/loadyaml_spec.rb | 36 + .../stdlib/spec/functions/lstrip_spec.rb | 35 + .../modules/stdlib/spec/functions/max_spec.rb | 21 + .../stdlib/spec/functions/member_spec.rb | 23 + .../stdlib/spec/functions/merge_spec.rb | 25 + .../modules/stdlib/spec/functions/min_spec.rb | 21 + .../stdlib/spec/functions/num2bool_spec.rb | 22 + .../stdlib/spec/functions/parsejson_spec.rb | 69 + .../stdlib/spec/functions/parseyaml_spec.rb | 86 + .../spec/functions/pick_default_spec.rb | 26 + .../stdlib/spec/functions/pick_spec.rb | 17 + .../stdlib/spec/functions/prefix_spec.rb | 29 + .../stdlib/spec/functions/private_spec.rb | 56 + .../stdlib/spec/functions/pw_hash_spec.rb | 69 + .../stdlib/spec/functions/range_spec.rb | 126 ++ .../spec/functions/regexpescape_spec.rb | 41 + .../stdlib/spec/functions/reject_spec.rb | 20 + .../stdlib/spec/functions/reverse_spec.rb | 33 + .../stdlib/spec/functions/rstrip_spec.rb | 35 + .../stdlib/spec/functions/seeded_rand_spec.rb | 58 + .../spec/functions/shell_escape_spec.rb | 27 + .../stdlib/spec/functions/shell_join_spec.rb | 28 + .../stdlib/spec/functions/shell_split_spec.rb | 29 + .../stdlib/spec/functions/shuffle_spec.rb | 38 + .../stdlib/spec/functions/size_spec.rb | 38 + .../stdlib/spec/functions/sort_spec.rb | 24 + .../stdlib/spec/functions/squeeze_spec.rb | 50 + .../stdlib/spec/functions/str2bool_spec.rb | 23 + .../spec/functions/str2saltedsha512_spec.rb | 17 + .../stdlib/spec/functions/strftime_spec.rb | 26 + .../stdlib/spec/functions/strip_spec.rb | 35 + .../stdlib/spec/functions/suffix_spec.rb | 40 + .../stdlib/spec/functions/swapcase_spec.rb | 40 + .../stdlib/spec/functions/time_spec.rb | 21 + .../stdlib/spec/functions/to_bytes_spec.rb | 72 + .../spec/functions/try_get_value_spec.rb | 100 + .../stdlib/spec/functions/type3x_spec.rb | 41 + .../stdlib/spec/functions/type_of_spec.rb | 25 + .../stdlib/spec/functions/type_spec.rb | 42 + .../stdlib/spec/functions/union_spec.rb | 25 + .../stdlib/spec/functions/unique_spec.rb | 29 + .../stdlib/spec/functions/unix2dos_spec.rb | 40 + .../stdlib/spec/functions/upcase_spec.rb | 26 + .../stdlib/spec/functions/uriescape_spec.rb | 36 + .../functions/validate_absolute_path_spec.rb | 73 + .../spec/functions/validate_array_spec.rb | 37 + .../spec/functions/validate_augeas_spec.rb | 75 + .../spec/functions/validate_bool_spec.rb | 36 + .../spec/functions/validate_cmd_spec.rb | 35 + .../functions/validate_email_address_spec.rb | 23 + .../spec/functions/validate_hash_spec.rb | 38 + .../spec/functions/validate_integer_spec.rb | 101 + .../functions/validate_ip_address_spec.rb | 65 + .../functions/validate_ipv4_address_spec.rb | 41 + .../functions/validate_ipv6_address_spec.rb | 60 + .../spec/functions/validate_legacy_spec.rb | 68 + .../spec/functions/validate_numeric_spec.rb | 100 + .../stdlib/spec/functions/validate_re_spec.rb | 56 + .../spec/functions/validate_slength_spec.rb | 72 + .../spec/functions/validate_string_spec.rb | 33 + .../validate_x509_rsa_key_pair_spec.rb | 180 ++ .../stdlib/spec/functions/values_at_spec.rb | 54 + .../stdlib/spec/functions/values_spec.rb | 24 + .../modules/stdlib/spec/functions/zip_spec.rb | 21 + .../monkey_patches/alias_should_to_must.rb | 9 + .../spec/monkey_patches/publicize_methods.rb | 11 + .../modules/stdlib/spec/spec_helper.rb | 8 + .../stdlib/spec/spec_helper_acceptance.rb | 55 + .../modules/stdlib/spec/spec_helper_local.rb | 31 + .../stdlib/spec/support/shared_data.rb | 38 + .../stdlib/spec/unit/ensure_resources_spec.rb | 22 + .../spec/unit/facter/facter_dot_d_spec.rb | 32 + .../spec/unit/facter/package_provider_spec.rb | 44 + .../spec/unit/facter/pe_version_spec.rb | 88 + .../stdlib/spec/unit/facter/root_home_spec.rb | 65 + .../spec/unit/facter/service_provider_spec.rb | 37 + .../unit/facter/util/puppet_settings_spec.rb | 37 + .../parser/functions/enclose_ipv6_spec.rb | 69 + .../parser/functions/is_absolute_path_spec.rb | 86 + .../puppet/provider/file_line/ruby_spec.rb | 447 ++++ .../spec/unit/puppet/type/anchor_spec.rb | 11 + .../spec/unit/puppet/type/file_line_spec.rb | 113 ++ .../modules/stdlib/types/absolutepath.pp | 2 + .../stdlib/types/compat/absolute_path.pp | 7 + .../modules/stdlib/types/compat/array.pp | 2 + .../modules/stdlib/types/compat/bool.pp | 2 + .../modules/stdlib/types/compat/float.pp | 19 + .../modules/stdlib/types/compat/hash.pp | 2 + .../modules/stdlib/types/compat/integer.pp | 23 + .../modules/stdlib/types/compat/ip_address.pp | 1 + .../modules/stdlib/types/compat/ipv4.pp | 2 + .../modules/stdlib/types/compat/ipv6.pp | 1 + .../modules/stdlib/types/compat/numeric.pp | 23 + .../modules/stdlib/types/compat/re.pp | 3 + .../modules/stdlib/types/compat/string.pp | 2 + .../modules/stdlib/types/httpsurl.pp | 1 + .../modules/stdlib/types/httpurl.pp | 1 + .../modules/stdlib/types/unixpath.pp | 2 + .../modules/stdlib/types/windowspath.pp | 1 + ...puppetdb_and_puppetexplorer_as_modules.txt | 60 + ...installing_puppetdb_and_puppetexplorer.txt | 184 ++ puppet.conf | 15 + puppetdb.conf | 4 + screens/master_main.png | Bin 0 -> 132011 bytes screens/node2_events.png | Bin 0 -> 93634 bytes vagrant_scripts/dns.sh | 27 + vagrant_scripts/master.sh | 17 + vagrant_scripts/node1.sh | 16 + vagrant_scripts/node2.sh | 16 + vagrant_shares/dns/NetworkManager.conf | 20 + vagrant_shares/dns/forward.kuzniatsou | 19 + vagrant_shares/dns/ifcfg-enp0s3 | 20 + vagrant_shares/dns/ifcfg-enp0s8 | 12 + vagrant_shares/dns/named.conf | 68 + vagrant_shares/dns/reverse.kuzniatsou | 20 + vagrant_shares/master/autosign.conf | 1 + vagrant_shares/master/site.pp | 11 + vagrant_shares/node1/puppet.conf | 11 + vagrant_shares/node2/puppet.conf | 12 + 1360 files changed, 66940 insertions(+) create mode 100644 Vagrantfile create mode 100644 [agent]-node1-puppet.conf create mode 100644 [agent]-node2-puppet.conf create mode 100644 environments/prod/environment.conf create mode 100644 environments/prod/manifests/site.pp create mode 100644 environments/prod/modules/mysql/CHANGELOG.md create mode 100644 environments/prod/modules/mysql/CONTRIBUTING.md create mode 100644 environments/prod/modules/mysql/Gemfile create mode 100644 environments/prod/modules/mysql/LICENSE create mode 100644 environments/prod/modules/mysql/NOTICE create mode 100644 environments/prod/modules/mysql/README.md create mode 100644 environments/prod/modules/mysql/Rakefile create mode 100644 environments/prod/modules/mysql/TODO create mode 100644 environments/prod/modules/mysql/checksums.json create mode 100644 environments/prod/modules/mysql/examples/backup.pp create mode 100644 environments/prod/modules/mysql/examples/bindings.pp create mode 100644 environments/prod/modules/mysql/examples/java.pp create mode 100644 environments/prod/modules/mysql/examples/mysql_database.pp create mode 100644 environments/prod/modules/mysql/examples/mysql_db.pp create mode 100644 environments/prod/modules/mysql/examples/mysql_grant.pp create mode 100644 environments/prod/modules/mysql/examples/mysql_plugin.pp create mode 100644 environments/prod/modules/mysql/examples/mysql_user.pp create mode 100644 environments/prod/modules/mysql/examples/perl.pp create mode 100644 environments/prod/modules/mysql/examples/python.pp create mode 100644 environments/prod/modules/mysql/examples/ruby.pp create mode 100644 environments/prod/modules/mysql/examples/server.pp create mode 100644 environments/prod/modules/mysql/examples/server/account_security.pp create mode 100644 environments/prod/modules/mysql/examples/server/config.pp create mode 100644 environments/prod/modules/mysql/lib/facter/mysql_server_id.rb create mode 100644 environments/prod/modules/mysql/lib/facter/mysql_version.rb create mode 100644 environments/prod/modules/mysql/lib/facter/mysqld_version.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_password.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/type/mysql_database.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/type/mysql_datadir.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/type/mysql_grant.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/type/mysql_plugin.rb create mode 100644 environments/prod/modules/mysql/lib/puppet/type/mysql_user.rb create mode 100644 environments/prod/modules/mysql/manifests/backup/mysqlbackup.pp create mode 100644 environments/prod/modules/mysql/manifests/backup/mysqldump.pp create mode 100644 environments/prod/modules/mysql/manifests/backup/xtrabackup.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/client_dev.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/daemon_dev.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/java.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/perl.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/php.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/python.pp create mode 100644 environments/prod/modules/mysql/manifests/bindings/ruby.pp create mode 100644 environments/prod/modules/mysql/manifests/client.pp create mode 100644 environments/prod/modules/mysql/manifests/client/install.pp create mode 100644 environments/prod/modules/mysql/manifests/db.pp create mode 100644 environments/prod/modules/mysql/manifests/params.pp create mode 100644 environments/prod/modules/mysql/manifests/server.pp create mode 100644 environments/prod/modules/mysql/manifests/server/account_security.pp create mode 100644 environments/prod/modules/mysql/manifests/server/backup.pp create mode 100644 environments/prod/modules/mysql/manifests/server/binarylog.pp create mode 100644 environments/prod/modules/mysql/manifests/server/config.pp create mode 100644 environments/prod/modules/mysql/manifests/server/install.pp create mode 100644 environments/prod/modules/mysql/manifests/server/installdb.pp create mode 100644 environments/prod/modules/mysql/manifests/server/monitor.pp create mode 100644 environments/prod/modules/mysql/manifests/server/mysqltuner.pp create mode 100644 environments/prod/modules/mysql/manifests/server/providers.pp create mode 100644 environments/prod/modules/mysql/manifests/server/root_password.pp create mode 100644 environments/prod/modules/mysql/manifests/server/service.pp create mode 100644 environments/prod/modules/mysql/metadata.json create mode 100644 environments/prod/modules/mysql/spec/acceptance/mysql_backup_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/mysql_db_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/mysql_helper.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/mysql_server_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/default.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml create mode 100644 environments/prod/modules/mysql/spec/acceptance/types/mysql_database_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb create mode 100644 environments/prod/modules/mysql/spec/acceptance/types/mysql_user_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/graceful_failures_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mycnf_template_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_bindings_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_client_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_server_account_security_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_server_backup_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_server_monitor_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb create mode 100644 environments/prod/modules/mysql/spec/classes/mysql_server_spec.rb create mode 100644 environments/prod/modules/mysql/spec/defines/mysql_db_spec.rb create mode 100644 environments/prod/modules/mysql/spec/spec.opts create mode 100644 environments/prod/modules/mysql/spec/spec_helper.rb create mode 100644 environments/prod/modules/mysql/spec/spec_helper_acceptance.rb create mode 100644 environments/prod/modules/mysql/spec/spec_helper_local.rb create mode 100644 environments/prod/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/facter/mysql_version_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/facter/mysqld_version_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb create mode 100644 environments/prod/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb create mode 100644 environments/prod/modules/mysql/templates/meb.cnf.erb create mode 100644 environments/prod/modules/mysql/templates/my.cnf.erb create mode 100644 environments/prod/modules/mysql/templates/my.cnf.pass.erb create mode 100755 environments/prod/modules/mysql/templates/mysqlbackup.sh.erb create mode 100644 environments/prod/modules/mysql/templates/xtrabackup.sh.erb create mode 100644 environments/prod/modules/stdlib/CHANGELOG.md create mode 100644 environments/prod/modules/stdlib/CONTRIBUTING.md create mode 100644 environments/prod/modules/stdlib/Gemfile create mode 100644 environments/prod/modules/stdlib/LICENSE create mode 100644 environments/prod/modules/stdlib/MAINTAINERS.md create mode 100644 environments/prod/modules/stdlib/NOTICE create mode 100644 environments/prod/modules/stdlib/README.markdown create mode 100644 environments/prod/modules/stdlib/README_DEVELOPER.markdown create mode 100644 environments/prod/modules/stdlib/README_SPECS.markdown create mode 100644 environments/prod/modules/stdlib/RELEASE_PROCESS.markdown create mode 100644 environments/prod/modules/stdlib/Rakefile create mode 100644 environments/prod/modules/stdlib/appveyor.yml create mode 100644 environments/prod/modules/stdlib/checksums.json create mode 100644 environments/prod/modules/stdlib/examples/file_line.pp create mode 100644 environments/prod/modules/stdlib/examples/has_interface_with.pp create mode 100644 environments/prod/modules/stdlib/examples/has_ip_address.pp create mode 100644 environments/prod/modules/stdlib/examples/has_ip_network.pp create mode 100644 environments/prod/modules/stdlib/examples/init.pp create mode 100644 environments/prod/modules/stdlib/lib/facter/facter_dot_d.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/package_provider.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/pe_version.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/puppet_settings.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/root_home.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/service_provider.rb create mode 100644 environments/prod/modules/stdlib/lib/facter/util/puppet_settings.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/deprecation.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_a.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_absolute_path.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_array.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_float.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_ip_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_numeric.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/is_string.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/length.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/type_of.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_array.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_hash.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_integer.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_ip_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_legacy.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_numeric.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_re.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_slength.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/functions/validate_string.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/abs.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/any2array.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/any2bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/assert_private.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/base64.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/basename.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2num.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2str.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/camelcase.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/capitalize.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/ceiling.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/chomp.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/chop.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/clamp.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/concat.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/convert_base.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/count.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/delete.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_at.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_values.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/deprecation.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/difference.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/dig.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/dig44.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/dirname.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/downcase.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/empty.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/flatten.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/floor.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/getparam.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/getvar.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/grep.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/has_key.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/hash.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/intersection.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_array.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_float.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_hash.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_integer.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/is_string.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/join.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/keys.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/loadjson.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/lstrip.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/max.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/member.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/merge.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/min.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/num2bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/parsejson.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/pick.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/pick_default.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/prefix.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/private.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/pry.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/range.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/reject.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/reverse.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/rstrip.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_join.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_split.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/shuffle.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/size.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/sort.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/squeeze.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/str2bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/strftime.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/strip.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/suffix.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/swapcase.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/time.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/type.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/type3x.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/union.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/unique.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/upcase.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/uriescape.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_array.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_re.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_string.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/values.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/values_at.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/parser/functions/zip.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/provider/file_line/ruby.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/type/anchor.rb create mode 100644 environments/prod/modules/stdlib/lib/puppet/type/file_line.rb create mode 100644 environments/prod/modules/stdlib/manifests/init.pp create mode 100644 environments/prod/modules/stdlib/manifests/stages.pp create mode 100644 environments/prod/modules/stdlib/metadata.json create mode 100755 environments/prod/modules/stdlib/spec/acceptance/abs_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/anchor_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/any2array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/base64_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/bool2num_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/build_csv.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/capitalize_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/ceiling_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/chomp_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/chop_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/clamp_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/concat_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/count_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/deep_merge_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/defined_with_params_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/delete_at_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/delete_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/delete_values_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/deprecation_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/difference_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/dirname_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/downcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/empty_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/ensure_resource_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/flatten_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/floor_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/get_module_path_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/getparam_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/getvar_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/grep_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/has_interface_with_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/has_ip_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/has_ip_network_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/has_key_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/intersection_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/is_a_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_domain_name_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_float_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_function_available_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_integer_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_ip_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_mac_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_numeric_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/is_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/join_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/keys_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/loadjson_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/loadyaml_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/lstrip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/max_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/member_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/merge_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/min_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/default.yml create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml create mode 100644 environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml create mode 100755 environments/prod/modules/stdlib/spec/acceptance/num2bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/parsejson_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/parseyaml_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/pick_default_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/pick_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/prefix_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/acceptance/pw_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/range_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/reject_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/reverse_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/rstrip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/shuffle_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/size_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/sort_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/squeeze_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/str2bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/strftime_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/strip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/suffix_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/swapcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/time_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/to_bytes_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/try_get_value_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/type_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/union_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/unique_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/upcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/uriescape_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_augeas_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_cmd_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_re_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_slength_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/validate_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/values_at_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/acceptance/zip_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/absolute_path_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/absolutepath_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/array_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/bool_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/float_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/hash_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/httpsurl_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/httpurl_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/integer_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/ip_address.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/ipv4_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/ipv6_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/numeric_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/string_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/unixpath_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/aliases/windowspath_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/fixtures/dscacheutil/root create mode 100644 environments/prod/modules/stdlib/spec/fixtures/lsuser/root create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/array.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/bool.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/float.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/hash.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/integer.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/numeric.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/string.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp create mode 100644 environments/prod/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp create mode 100755 environments/prod/modules/stdlib/spec/functions/abs_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/any2array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/any2bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/assert_private_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/base64_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/basename_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/bool2num_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/bool2str_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/camelcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/capitalize_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/ceiling_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/chomp_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/chop_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/clamp_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/concat_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/convert_base_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/count_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/deep_merge_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/defined_with_params_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/delete_at_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/delete_regex_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/delete_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/delete_undef_values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/delete_values_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/deprecation_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/difference_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/dig44_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/dig_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/dirname_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/dos2unix_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/downcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/empty_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/ensure_packages_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/ensure_resource_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/flatten_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/floor_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/fqdn_rotate_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/fqdn_uuid_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/get_module_path_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/getparam_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/getvar_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/grep_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/has_interface_with_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/has_ip_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/has_ip_network_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/has_key_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/intersection_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/is_a_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_domain_name_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_email_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_float_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_function_available.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_integer_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_ip_address_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/is_ipv4_address_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/is_ipv6_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_mac_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_numeric_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/is_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/join_keys_to_values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/join_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/keys_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/length.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/load_module_metadata_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/loadjson_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/loadyaml_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/lstrip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/max_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/member_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/merge_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/min_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/num2bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/parsejson_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/parseyaml_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/pick_default_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/pick_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/prefix_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/private_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/pw_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/range_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/regexpescape_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/reject_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/reverse_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/rstrip_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/seeded_rand_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/shell_escape_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/shell_join_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/shell_split_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/shuffle_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/size_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/sort_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/squeeze_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/str2bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/str2saltedsha512_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/strftime_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/strip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/suffix_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/swapcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/time_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/to_bytes_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/try_get_value_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/type3x_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/type_of_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/type_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/union_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/unique_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/unix2dos_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/upcase_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/uriescape_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_absolute_path_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_array_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_augeas_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_bool_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_cmd_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/validate_email_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_hash_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_integer_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/validate_ip_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/functions/validate_legacy_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_numeric_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_re_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_slength_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_string_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/values_at_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/values_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/functions/zip_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb create mode 100755 environments/prod/modules/stdlib/spec/monkey_patches/publicize_methods.rb create mode 100755 environments/prod/modules/stdlib/spec/spec_helper.rb create mode 100755 environments/prod/modules/stdlib/spec/spec_helper_acceptance.rb create mode 100644 environments/prod/modules/stdlib/spec/spec_helper_local.rb create mode 100644 environments/prod/modules/stdlib/spec/support/shared_data.rb create mode 100644 environments/prod/modules/stdlib/spec/unit/ensure_resources_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/unit/facter/package_provider_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/facter/pe_version_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/facter/root_home_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/unit/facter/service_provider_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb create mode 100644 environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb create mode 100755 environments/prod/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb create mode 100644 environments/prod/modules/stdlib/types/absolutepath.pp create mode 100644 environments/prod/modules/stdlib/types/compat/absolute_path.pp create mode 100644 environments/prod/modules/stdlib/types/compat/array.pp create mode 100644 environments/prod/modules/stdlib/types/compat/bool.pp create mode 100644 environments/prod/modules/stdlib/types/compat/float.pp create mode 100644 environments/prod/modules/stdlib/types/compat/hash.pp create mode 100644 environments/prod/modules/stdlib/types/compat/integer.pp create mode 100644 environments/prod/modules/stdlib/types/compat/ip_address.pp create mode 100644 environments/prod/modules/stdlib/types/compat/ipv4.pp create mode 100644 environments/prod/modules/stdlib/types/compat/ipv6.pp create mode 100644 environments/prod/modules/stdlib/types/compat/numeric.pp create mode 100644 environments/prod/modules/stdlib/types/compat/re.pp create mode 100644 environments/prod/modules/stdlib/types/compat/string.pp create mode 100644 environments/prod/modules/stdlib/types/httpsurl.pp create mode 100644 environments/prod/modules/stdlib/types/httpurl.pp create mode 100644 environments/prod/modules/stdlib/types/unixpath.pp create mode 100644 environments/prod/modules/stdlib/types/windowspath.pp create mode 100644 environments/production/environment.conf create mode 100644 environments/production/manifests/site.pp create mode 100644 environments/production/modules/mysql/CHANGELOG.md create mode 100644 environments/production/modules/mysql/CONTRIBUTING.md create mode 100644 environments/production/modules/mysql/Gemfile create mode 100644 environments/production/modules/mysql/LICENSE create mode 100644 environments/production/modules/mysql/NOTICE create mode 100644 environments/production/modules/mysql/README.md create mode 100644 environments/production/modules/mysql/Rakefile create mode 100644 environments/production/modules/mysql/TODO create mode 100644 environments/production/modules/mysql/checksums.json create mode 100644 environments/production/modules/mysql/examples/backup.pp create mode 100644 environments/production/modules/mysql/examples/bindings.pp create mode 100644 environments/production/modules/mysql/examples/java.pp create mode 100644 environments/production/modules/mysql/examples/mysql_database.pp create mode 100644 environments/production/modules/mysql/examples/mysql_db.pp create mode 100644 environments/production/modules/mysql/examples/mysql_grant.pp create mode 100644 environments/production/modules/mysql/examples/mysql_plugin.pp create mode 100644 environments/production/modules/mysql/examples/mysql_user.pp create mode 100644 environments/production/modules/mysql/examples/perl.pp create mode 100644 environments/production/modules/mysql/examples/python.pp create mode 100644 environments/production/modules/mysql/examples/ruby.pp create mode 100644 environments/production/modules/mysql/examples/server.pp create mode 100644 environments/production/modules/mysql/examples/server/account_security.pp create mode 100644 environments/production/modules/mysql/examples/server/config.pp create mode 100644 environments/production/modules/mysql/lib/facter/mysql_server_id.rb create mode 100644 environments/production/modules/mysql/lib/facter/mysql_version.rb create mode 100644 environments/production/modules/mysql/lib/facter/mysqld_version.rb create mode 100644 environments/production/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb create mode 100644 environments/production/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb create mode 100644 environments/production/modules/mysql/lib/puppet/parser/functions/mysql_password.rb create mode 100644 environments/production/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb create mode 100644 environments/production/modules/mysql/lib/puppet/type/mysql_database.rb create mode 100644 environments/production/modules/mysql/lib/puppet/type/mysql_datadir.rb create mode 100644 environments/production/modules/mysql/lib/puppet/type/mysql_grant.rb create mode 100644 environments/production/modules/mysql/lib/puppet/type/mysql_plugin.rb create mode 100644 environments/production/modules/mysql/lib/puppet/type/mysql_user.rb create mode 100644 environments/production/modules/mysql/manifests/backup/mysqlbackup.pp create mode 100644 environments/production/modules/mysql/manifests/backup/mysqldump.pp create mode 100644 environments/production/modules/mysql/manifests/backup/xtrabackup.pp create mode 100644 environments/production/modules/mysql/manifests/bindings.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/client_dev.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/daemon_dev.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/java.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/perl.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/php.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/python.pp create mode 100644 environments/production/modules/mysql/manifests/bindings/ruby.pp create mode 100644 environments/production/modules/mysql/manifests/client.pp create mode 100644 environments/production/modules/mysql/manifests/client/install.pp create mode 100644 environments/production/modules/mysql/manifests/db.pp create mode 100644 environments/production/modules/mysql/manifests/params.pp create mode 100644 environments/production/modules/mysql/manifests/server.pp create mode 100644 environments/production/modules/mysql/manifests/server/account_security.pp create mode 100644 environments/production/modules/mysql/manifests/server/backup.pp create mode 100644 environments/production/modules/mysql/manifests/server/binarylog.pp create mode 100644 environments/production/modules/mysql/manifests/server/config.pp create mode 100644 environments/production/modules/mysql/manifests/server/install.pp create mode 100644 environments/production/modules/mysql/manifests/server/installdb.pp create mode 100644 environments/production/modules/mysql/manifests/server/monitor.pp create mode 100644 environments/production/modules/mysql/manifests/server/mysqltuner.pp create mode 100644 environments/production/modules/mysql/manifests/server/providers.pp create mode 100644 environments/production/modules/mysql/manifests/server/root_password.pp create mode 100644 environments/production/modules/mysql/manifests/server/service.pp create mode 100644 environments/production/modules/mysql/metadata.json create mode 100644 environments/production/modules/mysql/spec/acceptance/mysql_backup_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/mysql_db_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/mysql_helper.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/mysql_server_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/default.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml create mode 100644 environments/production/modules/mysql/spec/acceptance/types/mysql_database_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb create mode 100644 environments/production/modules/mysql/spec/acceptance/types/mysql_user_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/graceful_failures_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mycnf_template_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_bindings_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_client_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_server_account_security_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_server_backup_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_server_monitor_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb create mode 100644 environments/production/modules/mysql/spec/classes/mysql_server_spec.rb create mode 100644 environments/production/modules/mysql/spec/defines/mysql_db_spec.rb create mode 100644 environments/production/modules/mysql/spec/spec.opts create mode 100644 environments/production/modules/mysql/spec/spec_helper.rb create mode 100644 environments/production/modules/mysql/spec/spec_helper_acceptance.rb create mode 100644 environments/production/modules/mysql/spec/spec_helper_local.rb create mode 100644 environments/production/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/facter/mysql_version_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/facter/mysqld_version_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb create mode 100644 environments/production/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb create mode 100644 environments/production/modules/mysql/templates/meb.cnf.erb create mode 100644 environments/production/modules/mysql/templates/my.cnf.erb create mode 100644 environments/production/modules/mysql/templates/my.cnf.pass.erb create mode 100755 environments/production/modules/mysql/templates/mysqlbackup.sh.erb create mode 100644 environments/production/modules/mysql/templates/xtrabackup.sh.erb create mode 100644 environments/production/modules/nginx/ChangeLog create mode 100644 environments/production/modules/nginx/LICENSE create mode 100644 environments/production/modules/nginx/Modulefile create mode 100644 environments/production/modules/nginx/README create mode 100644 environments/production/modules/nginx/README.markdown create mode 100644 environments/production/modules/nginx/manifests/config.pp create mode 100644 environments/production/modules/nginx/manifests/init.pp create mode 100644 environments/production/modules/nginx/manifests/package.pp create mode 100644 environments/production/modules/nginx/manifests/package/debian.pp create mode 100644 environments/production/modules/nginx/manifests/package/redhat.pp create mode 100644 environments/production/modules/nginx/manifests/package/suse.pp create mode 100644 environments/production/modules/nginx/manifests/params.pp create mode 100644 environments/production/modules/nginx/manifests/resource/location.pp create mode 100644 environments/production/modules/nginx/manifests/resource/upstream.pp create mode 100644 environments/production/modules/nginx/manifests/resource/vhost.pp create mode 100644 environments/production/modules/nginx/manifests/service.pp create mode 100644 environments/production/modules/nginx/metadata.json create mode 100644 environments/production/modules/nginx/templates/conf.d/nginx.conf.erb create mode 100644 environments/production/modules/nginx/templates/conf.d/proxy.conf.erb create mode 100644 environments/production/modules/nginx/templates/conf.d/upstream.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_footer.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_header.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_location_alias.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_location_directory.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_location_proxy.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_location_stub_status.erb create mode 100644 environments/production/modules/nginx/templates/vhost/vhost_ssl_header.erb create mode 100644 environments/production/modules/nginx/tests/init.pp create mode 100644 environments/production/modules/nginx/tests/location_alias.pp create mode 100644 environments/production/modules/nginx/tests/upstream.pp create mode 100644 environments/production/modules/nginx/tests/vhost.pp create mode 100644 environments/production/modules/nginx/tests/vhost_ssl.erb create mode 100644 environments/production/modules/staging/CHANGELOG.md create mode 100644 environments/production/modules/staging/CONTRIBUTING.md create mode 100644 environments/production/modules/staging/Gemfile create mode 100644 environments/production/modules/staging/LICENSE create mode 100644 environments/production/modules/staging/README.md create mode 100644 environments/production/modules/staging/Rakefile create mode 100644 environments/production/modules/staging/checksums.json create mode 100644 environments/production/modules/staging/docs/deploy.html create mode 100644 environments/production/modules/staging/docs/extract.html create mode 100644 environments/production/modules/staging/docs/file.html create mode 100644 environments/production/modules/staging/docs/init.html create mode 100644 environments/production/modules/staging/examples/deploy.pp create mode 100644 environments/production/modules/staging/examples/extract.pp create mode 100644 environments/production/modules/staging/examples/file.pp create mode 100644 environments/production/modules/staging/examples/init.pp create mode 100644 environments/production/modules/staging/examples/scope_defaults.pp create mode 100644 environments/production/modules/staging/examples/staging_parse.pp create mode 100644 environments/production/modules/staging/files/sample create mode 100644 environments/production/modules/staging/files/sample.tar.bz2 create mode 100644 environments/production/modules/staging/files/sample.tar.gz create mode 100644 environments/production/modules/staging/lib/facter/staging_http_get.rb create mode 100644 environments/production/modules/staging/lib/facter/staging_windir.rb create mode 100644 environments/production/modules/staging/lib/puppet/parser/functions/scope_defaults.rb create mode 100644 environments/production/modules/staging/lib/puppet/parser/functions/staging_parse.rb create mode 100644 environments/production/modules/staging/manifests/deploy.pp create mode 100644 environments/production/modules/staging/manifests/extract.pp create mode 100644 environments/production/modules/staging/manifests/file.pp create mode 100644 environments/production/modules/staging/manifests/init.pp create mode 100644 environments/production/modules/staging/manifests/params.pp create mode 100644 environments/production/modules/staging/metadata.json create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/centos-511-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64-pe.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/centos-72-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/debian-78-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/debian-82-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-5.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-6.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-7.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-7.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-8.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-12.04.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-14.04.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-16.04.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/fedora-24-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/fedora-25-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1204-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml create mode 100644 environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1604-x64.yml create mode 100644 environments/production/modules/staging/spec/classes/coverage_spec.rb create mode 100644 environments/production/modules/staging/spec/default_facts.yml create mode 100644 environments/production/modules/staging/spec/defines/staging_deploy_spec.rb create mode 100644 environments/production/modules/staging/spec/defines/staging_extract_spec.rb create mode 100644 environments/production/modules/staging/spec/defines/staging_file_spec.rb create mode 100644 environments/production/modules/staging/spec/fixtures/hiera.yaml create mode 100644 environments/production/modules/staging/spec/spec.opts create mode 100644 environments/production/modules/staging/spec/spec_helper.rb create mode 100644 environments/production/modules/staging/spec/unit/puppet/parser/functions/scope_defaults_spec.rb create mode 100644 environments/production/modules/staging/spec/unit/puppet/parser/functions/staging_parse_spec.rb create mode 100644 environments/production/modules/stdlib/CHANGELOG.md create mode 100644 environments/production/modules/stdlib/CONTRIBUTING.md create mode 100644 environments/production/modules/stdlib/Gemfile create mode 100644 environments/production/modules/stdlib/LICENSE create mode 100644 environments/production/modules/stdlib/MAINTAINERS.md create mode 100644 environments/production/modules/stdlib/NOTICE create mode 100644 environments/production/modules/stdlib/README.markdown create mode 100644 environments/production/modules/stdlib/README_DEVELOPER.markdown create mode 100644 environments/production/modules/stdlib/README_SPECS.markdown create mode 100644 environments/production/modules/stdlib/RELEASE_PROCESS.markdown create mode 100644 environments/production/modules/stdlib/Rakefile create mode 100644 environments/production/modules/stdlib/appveyor.yml create mode 100644 environments/production/modules/stdlib/checksums.json create mode 100644 environments/production/modules/stdlib/examples/file_line.pp create mode 100644 environments/production/modules/stdlib/examples/has_interface_with.pp create mode 100644 environments/production/modules/stdlib/examples/has_ip_address.pp create mode 100644 environments/production/modules/stdlib/examples/has_ip_network.pp create mode 100644 environments/production/modules/stdlib/examples/init.pp create mode 100644 environments/production/modules/stdlib/lib/facter/facter_dot_d.rb create mode 100644 environments/production/modules/stdlib/lib/facter/package_provider.rb create mode 100644 environments/production/modules/stdlib/lib/facter/pe_version.rb create mode 100644 environments/production/modules/stdlib/lib/facter/puppet_settings.rb create mode 100644 environments/production/modules/stdlib/lib/facter/root_home.rb create mode 100644 environments/production/modules/stdlib/lib/facter/service_provider.rb create mode 100644 environments/production/modules/stdlib/lib/facter/util/puppet_settings.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/deprecation.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_a.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_absolute_path.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_array.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_float.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_ip_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_numeric.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/is_string.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/length.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/type_of.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_array.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_hash.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_integer.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_ip_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_legacy.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_numeric.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_re.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_slength.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/functions/validate_string.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/abs.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/any2array.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/any2bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/assert_private.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/base64.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/basename.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/bool2num.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/bool2str.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/camelcase.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/capitalize.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/ceiling.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/chomp.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/chop.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/clamp.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/concat.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/convert_base.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/count.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/delete.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/delete_at.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/delete_values.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/deprecation.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/difference.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/dig.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/dig44.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/dirname.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/downcase.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/empty.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/flatten.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/floor.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/getparam.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/getvar.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/grep.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/has_key.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/hash.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/intersection.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_array.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_float.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_hash.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_integer.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/is_string.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/join.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/keys.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/loadjson.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/lstrip.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/max.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/member.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/merge.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/min.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/num2bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/parsejson.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/pick.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/pick_default.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/prefix.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/private.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/pry.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/range.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/reject.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/reverse.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/rstrip.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/shell_join.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/shell_split.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/shuffle.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/size.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/sort.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/squeeze.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/str2bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/strftime.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/strip.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/suffix.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/swapcase.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/time.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/type.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/type3x.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/union.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/unique.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/upcase.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/uriescape.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_array.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_re.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_string.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/values.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/values_at.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/parser/functions/zip.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/provider/file_line/ruby.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/type/anchor.rb create mode 100644 environments/production/modules/stdlib/lib/puppet/type/file_line.rb create mode 100644 environments/production/modules/stdlib/manifests/init.pp create mode 100644 environments/production/modules/stdlib/manifests/stages.pp create mode 100644 environments/production/modules/stdlib/metadata.json create mode 100755 environments/production/modules/stdlib/spec/acceptance/abs_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/anchor_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/any2array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/base64_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/bool2num_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/build_csv.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/capitalize_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/ceiling_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/chomp_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/chop_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/clamp_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/concat_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/count_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/deep_merge_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/defined_with_params_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/delete_at_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/delete_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/delete_values_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/deprecation_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/difference_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/dirname_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/downcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/empty_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/ensure_resource_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/flatten_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/floor_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/get_module_path_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/getparam_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/getvar_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/grep_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/has_interface_with_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/has_ip_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/has_ip_network_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/has_key_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/intersection_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/is_a_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_domain_name_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_float_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_function_available_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_integer_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_ip_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_mac_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_numeric_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/is_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/join_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/keys_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/loadjson_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/loadyaml_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/lstrip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/max_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/member_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/merge_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/min_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/default.yml create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml create mode 100644 environments/production/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml create mode 100755 environments/production/modules/stdlib/spec/acceptance/num2bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/parsejson_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/parseyaml_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/pick_default_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/pick_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/prefix_spec.rb create mode 100644 environments/production/modules/stdlib/spec/acceptance/pw_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/range_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/reject_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/reverse_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/rstrip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/shuffle_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/size_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/sort_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/squeeze_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/str2bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/strftime_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/strip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/suffix_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/swapcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/time_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/to_bytes_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/try_get_value_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/type_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/union_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/unique_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/upcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/uriescape_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_augeas_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_cmd_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_re_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_slength_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/validate_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/values_at_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/acceptance/zip_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/absolute_path_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/absolutepath_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/array_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/bool_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/float_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/hash_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/httpsurl_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/httpurl_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/integer_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/ip_address.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/ipv4_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/ipv6_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/numeric_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/string_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/unixpath_spec.rb create mode 100644 environments/production/modules/stdlib/spec/aliases/windowspath_spec.rb create mode 100644 environments/production/modules/stdlib/spec/fixtures/dscacheutil/root create mode 100644 environments/production/modules/stdlib/spec/fixtures/lsuser/root create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/array.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/bool.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/float.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/hash.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/integer.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/numeric.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/string.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp create mode 100644 environments/production/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp create mode 100755 environments/production/modules/stdlib/spec/functions/abs_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/any2array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/any2bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/assert_private_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/base64_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/basename_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/bool2num_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/bool2str_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/camelcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/capitalize_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/ceiling_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/chomp_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/chop_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/clamp_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/concat_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/convert_base_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/count_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/deep_merge_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/defined_with_params_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/delete_at_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/delete_regex_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/delete_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/delete_undef_values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/delete_values_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/deprecation_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/difference_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/dig44_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/dig_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/dirname_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/dos2unix_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/downcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/empty_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/ensure_packages_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/ensure_resource_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/flatten_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/floor_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/fqdn_rotate_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/fqdn_uuid_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/get_module_path_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/getparam_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/getvar_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/grep_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/has_interface_with_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/has_ip_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/has_ip_network_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/has_key_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/intersection_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/is_a_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_domain_name_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_email_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_float_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_function_available.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_integer_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_ip_address_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/is_ipv4_address_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/is_ipv6_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_mac_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_numeric_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/is_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/join_keys_to_values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/join_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/keys_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/length.rb create mode 100755 environments/production/modules/stdlib/spec/functions/load_module_metadata_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/loadjson_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/loadyaml_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/lstrip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/max_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/member_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/merge_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/min_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/num2bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/parsejson_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/parseyaml_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/pick_default_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/pick_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/prefix_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/private_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/pw_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/range_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/regexpescape_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/reject_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/reverse_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/rstrip_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/seeded_rand_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/shell_escape_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/shell_join_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/shell_split_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/shuffle_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/size_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/sort_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/squeeze_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/str2bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/str2saltedsha512_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/strftime_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/strip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/suffix_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/swapcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/time_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/to_bytes_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/try_get_value_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/type3x_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/type_of_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/type_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/union_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/unique_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/unix2dos_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/upcase_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/uriescape_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_absolute_path_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_array_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_augeas_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_bool_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_cmd_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/validate_email_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_hash_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_integer_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/validate_ip_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb create mode 100644 environments/production/modules/stdlib/spec/functions/validate_legacy_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_numeric_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_re_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_slength_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_string_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/values_at_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/values_spec.rb create mode 100755 environments/production/modules/stdlib/spec/functions/zip_spec.rb create mode 100755 environments/production/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb create mode 100755 environments/production/modules/stdlib/spec/monkey_patches/publicize_methods.rb create mode 100755 environments/production/modules/stdlib/spec/spec_helper.rb create mode 100755 environments/production/modules/stdlib/spec/spec_helper_acceptance.rb create mode 100644 environments/production/modules/stdlib/spec/spec_helper_local.rb create mode 100644 environments/production/modules/stdlib/spec/support/shared_data.rb create mode 100644 environments/production/modules/stdlib/spec/unit/ensure_resources_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb create mode 100644 environments/production/modules/stdlib/spec/unit/facter/package_provider_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/facter/pe_version_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/facter/root_home_spec.rb create mode 100644 environments/production/modules/stdlib/spec/unit/facter/service_provider_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb create mode 100644 environments/production/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb create mode 100644 environments/production/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb create mode 100755 environments/production/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb create mode 100644 environments/production/modules/stdlib/types/absolutepath.pp create mode 100644 environments/production/modules/stdlib/types/compat/absolute_path.pp create mode 100644 environments/production/modules/stdlib/types/compat/array.pp create mode 100644 environments/production/modules/stdlib/types/compat/bool.pp create mode 100644 environments/production/modules/stdlib/types/compat/float.pp create mode 100644 environments/production/modules/stdlib/types/compat/hash.pp create mode 100644 environments/production/modules/stdlib/types/compat/integer.pp create mode 100644 environments/production/modules/stdlib/types/compat/ip_address.pp create mode 100644 environments/production/modules/stdlib/types/compat/ipv4.pp create mode 100644 environments/production/modules/stdlib/types/compat/ipv6.pp create mode 100644 environments/production/modules/stdlib/types/compat/numeric.pp create mode 100644 environments/production/modules/stdlib/types/compat/re.pp create mode 100644 environments/production/modules/stdlib/types/compat/string.pp create mode 100644 environments/production/modules/stdlib/types/httpsurl.pp create mode 100644 environments/production/modules/stdlib/types/httpurl.pp create mode 100644 environments/production/modules/stdlib/types/unixpath.pp create mode 100644 environments/production/modules/stdlib/types/windowspath.pp create mode 100644 manuals/Installing_puppetdb_and_puppetexplorer_as_modules.txt create mode 100644 manuals/Manual_installing_puppetdb_and_puppetexplorer.txt create mode 100644 puppet.conf create mode 100644 puppetdb.conf create mode 100644 screens/master_main.png create mode 100644 screens/node2_events.png create mode 100644 vagrant_scripts/dns.sh create mode 100644 vagrant_scripts/master.sh create mode 100644 vagrant_scripts/node1.sh create mode 100644 vagrant_scripts/node2.sh create mode 100644 vagrant_shares/dns/NetworkManager.conf create mode 100644 vagrant_shares/dns/forward.kuzniatsou create mode 100644 vagrant_shares/dns/ifcfg-enp0s3 create mode 100644 vagrant_shares/dns/ifcfg-enp0s8 create mode 100644 vagrant_shares/dns/named.conf create mode 100644 vagrant_shares/dns/reverse.kuzniatsou create mode 100644 vagrant_shares/master/autosign.conf create mode 100644 vagrant_shares/master/site.pp create mode 100644 vagrant_shares/node1/puppet.conf create mode 100644 vagrant_shares/node2/puppet.conf diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..ee0a5df --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,56 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + config.vm.define "dns" do |dns| + dns.vm.box = "sbeliakou/centos-7.2-x86_64-minimal" + dns.vm.hostname = "dns.kuzniatsou.local" + dns.vm.synced_folder "vagrant_shares/dns", "/tmp/dns" + dns.vm.network "private_network", ip: "192.168.33.99" + dns.vm.provider "virtualbox" do |vb| + vb.memory = "512" + vb.cpus = "1" + dns.vm.provision "shell", path: "./vagrant_scripts/dns.sh" + end + end + + config.vm.define "master1" do |master1| + master1.vm.box = "sbeliakou/centos-7.2-x86_64-minimal" + master1.vm.hostname = "master1.kuzniatsou.local" + master1.vm.synced_folder "vagrant_shares/master", "/tmp/master1" + master1.vm.network "private_network", ip: "192.168.33.190" + master1.vm.provider "virtualbox" do |vb| + vb.memory = "4096" + vb.cpus = "2" + master1.vm.provision "shell", path: "./vagrant_scripts/master.sh" + end + end + + config.vm.define "node1" do |node1| + node1.vm.box = "sbeliakou/centos-7.2-x86_64-minimal" + node1.vm.hostname = "node1.kuzniatsou.local" + node1.vm.synced_folder "vagrant_shares/node1", "/tmp/node1" + node1.vm.network "private_network", ip: "192.168.33.91" + node1.vm.provider "virtualbox" do |vb| + vb.memory = "1024" + vb.cpus = "1" + node1.vm.provision "shell", path: "./vagrant_scripts/node1.sh" + end + end + + config.vm.define "node2" do |node2| + node2.vm.box = "sbeliakou/centos-7.2-x86_64-minimal" + node2.vm.hostname = "node2.kuzniatsou.local" + node2.vm.synced_folder "vagrant_shares/node2", "/tmp/node2" + node2.vm.network "private_network", ip: "192.168.33.92" + node2.vm.provider "virtualbox" do |vb| + vb.memory = "1024" + vb.cpus = "1" + node2.vm.provision "shell", path: "./vagrant_scripts/node2.sh" + end + end +end diff --git a/[agent]-node1-puppet.conf b/[agent]-node1-puppet.conf new file mode 100644 index 0000000..78fda46 --- /dev/null +++ b/[agent]-node1-puppet.conf @@ -0,0 +1,5 @@ +[agent] + use_srv_records = true + srv_domain = kuzniatsou.local + runinterval = 60s + waitforcert = 20s \ No newline at end of file diff --git a/[agent]-node2-puppet.conf b/[agent]-node2-puppet.conf new file mode 100644 index 0000000..9af06d5 --- /dev/null +++ b/[agent]-node2-puppet.conf @@ -0,0 +1,6 @@ +[agent] + use_srv_records = true + srv_domain = kuzniatsou.local + environment = prod + runinterval = 60s + waitforcert = 20s \ No newline at end of file diff --git a/environments/prod/environment.conf b/environments/prod/environment.conf new file mode 100644 index 0000000..8f41a28 --- /dev/null +++ b/environments/prod/environment.conf @@ -0,0 +1,19 @@ +# Each environment can have an environment.conf file. Its settings will only +# affect its own environment. See docs for more info: +# https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html + +# Any unspecified settings use default values; some of those defaults are based +# on puppet.conf settings. + +# If these settings include relative file paths, they'll be resolved relative to +# this environment's directory. + +# Allowed settings and default values: + +# modulepath = ./modules:$basemodulepath +# manifest = (default_manifest from puppet.conf, which defaults to ./manifests) +# config_version = (no script; Puppet will use the time the catalog was compiled) +# environment_timeout = (environment_timeout from puppet.conf, which defaults to 0) + # Note: unless you have a specific reason, we recommend only setting + # environment_timeout in puppet.conf. +#modulepath =/etc/puppetlabs/code/environments/prod/modules diff --git a/environments/prod/manifests/site.pp b/environments/prod/manifests/site.pp new file mode 100644 index 0000000..057d1b0 --- /dev/null +++ b/environments/prod/manifests/site.pp @@ -0,0 +1,17 @@ +node node2.kuzniatsou.local { + include '::mysql::server' + + +# Ensure that the database 'test_mdb' is created, the user 'test_user' with assigned privileges exists +mysql::db { 'prod_mdb': + user => 'prod_user', + password => 'password', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} + +#Ensure package 'nginx' installed +package { 'nginx': + ensure => 'installed', + } +} diff --git a/environments/prod/modules/mysql/CHANGELOG.md b/environments/prod/modules/mysql/CHANGELOG.md new file mode 100644 index 0000000..5f07f6e --- /dev/null +++ b/environments/prod/modules/mysql/CHANGELOG.md @@ -0,0 +1,770 @@ +## Supported Release 3.10.0 +### Summary +This release includes new features for setting TLS options on a mysql user, a new parameter to allow specifying tool to import sql files, as well as various bugfixes. + +#### Features +- (MODULES-3879) Adds `import_cat_cmd` parameter to specify the command to read sql files +- Adds support for setting `tls_options` in `mysql_user` + +#### Bugfixes +- (MODULES-3557) Adds Ubuntu 16.04 package names for language bindings +- (MODULES-3907) Adds MySQL/Percona 5.7 initialize on fresh deploy + +## Supported Release 3.9.0 +### Summary +This release adds Percona 5.7 support and compatibility with Ubuntu 16.04, in addition to various bugfixes. + +#### Features +- (MODULES-3441) Adds the `mysqld_version` fact +- (MODULES-3513) Adds a new backup dump parameter `maxallowedpacket` +- Adds new parameter `xtrabackup_package_name` to `mysql::backup::xtrabackup` class +- Adds ability to revoke GRANT privilege + +#### Bugfixes +- Fixes a bug where `mysql_user` fails if facter cannot retrieve fqdn. +- Fix global parameter usage in backup script +- Adds support for `puppet-staging` version `2.0.0` +- (MODULES-3601) Moves binary logging configuration to take place after package install +- (MODULES-3711) Add limit to mysql server ID generated value +- (MODULES-3698) Fixes defaults for SLES12 +- Updates user name length restrictions for MySQL version 5.7.8 and above. +- Fixes a bug where error log is not writable by owner + +## Supported Release 3.8.0 +###Summary +This release adds Percona 5.7 support and compatibility with Ubuntu 16.04, in addition to various bugfixes. + +#### Features +- Adds support for Percona 5.7 +- Adds support for Ubuntu 16.04 (Xenial) + +#### Known Limitations +- The mysqlbackup.sh script will not work on MySQL 5.7.0 and up. + +#### Bugfixes +- Use mysql_install_db only with uniq defaults-extra-file +- Updates mysqlbackup.sh to ensure backup directory exist +- Loosen MariaDB recognition to fix it on Debian 8 +- Allow mysql::backup::mysqldump to access root_group in tests +- Fixed problem with ignoring parameters from global configs +- Fixes ordering issue that initialized mysqld before config is set +- (MODULES-1256) Fix parameters on OpenSUSE 12 +- Fixes install errors on Debian-based OS by configuring the base of includedir +- Configure the configfile location for mariadb +- Default mysqld_type return value should be 'mysql' if another type is not detected +- Make sure that bzip2 is installed before setting up the cron tab job using mysqlbackup.sh +- Fixes path issue on FreeBSD +- Check that /var/lib/mysql actually contains files +- Removes mysql regex when checking type +- (MODULES-2111) Add the system database to user related actions +- Updates default group for logfiles on Debian-based OS to 'adm' +- Fixes an issue with Amazon linux major release 4 installation +- Fixes 'mysql_install_db' script support on Gentoo +- Removes erroneous anchors to mysql::client from mysql::db +- Adds path to be able to find MySQL 5.5 installation on CentOS + +## Supported Release 3.7.0 +###Summary + +A large release with several new features. Also includes a considerable amount of bugfixes, many around compatibility and improvements to current functionality. + +#### Features + +- Now uses mariadb in OpenSuSE >= 13.1. +- Switch to rspec-puppet-facts. +- Additional function to check if table exists before grant. +- Add ability to input password hash directly. +- Now checking major release instead of specific release. +- Debian 8 support. + +#### Bugfixes + +- Minor doc update. +- Fixes improper use of function `warn` in backup manifest of server. +- Fixes to Compatibility with PE 3.3. +- Fixes `when not managing config file` in `mysql_server_spec`. +- Improved user validation and munging. +- Fixes fetching the mysql_user password for MySQL >=5.7.6. +- Fixes unique server_id within my.cnf, the issue were the entire mac address was not being read in to generate the id. +- Corrects the daemon_dev_package_name for mariadb on redhat. +- Fix version compare to properly suppress show_diff for root password. +- Fixes to ensure compatibility with future parser. +- Solaris removed from PE in metadata as its not supported. +- Use MYSQL_PWD to avoid mysqldump warnings. +- Use temp cnf file instead of env variable which creates acceptance test failures. +- No longer hash passwords that are already hashed. +- Fix Gemfile to work with ruby 1.8.7. +- Fixed MySQL 5.7.6++ compatibility. +- Fixing error when disabling service management and the service does not exist. +- Ubuntu vivid should use systemd not upstart. +- Fixed new mysql_datadir provider on CentOS for MySQl 5.7.6 compatibility. +- Ensure if service restart to wait till mysql is up. +- Move all dependencies to not have them in case of service unmanaged. +- Re-Added the ability to set a empty string as option parameter. +- Fixes edge-case with dropping pre-existing users with grants. +- Fix logic for choosing rspec version. +- Refactored main acceptance suite. +- Skip idempotency tests on test cells that do have PUP-5016 unfixed. +- Fix tmpdir to be shared across examples. +- Update to current msync configs [006831f]. +- Fix mysql_grant with MySQL ANSI_QUOTES mode. +- Generate .my.cnf for all sections. + +## Supported Release 3.6.2 +###Summary + +Small release for support of newer PE versions. This increments the version of PE in the metadata.json file. + +## 2015-09-22 - Supported Release 3.6.1 +### Summary +This is a security and bugfix release that fixes incorrect username truncation in the munge for the mysql_user type, incorrect function used in `mysql::server::backup` and fixes compatibility issues with PE 3.3.x. + +#### Bugfixes +- Loosen the regex in mysql_user munging so the username is not unintentionally truncated. +- Use `warning()` not `warn()` +- Metadata had inadvertantly dropped 3.3.x support +- Some 3.3.x compatibility issues in `mysqltuner` were corrected + +## 2015-08-10 - Supported Release 3.6.0 +### Summary +This release adds the ability to use mysql::db and `mysql_*` types against unmanaged or external mysql instances. + +#### Features +- Add ability to use mysql::db WITHOUT mysql::server (ie, externally) +- Add prescript attribute to mysql::server::backup for xtrabackup +- Add postscript ability to xtrabackup provider. + +#### Bugfixes +- Fix default root passwords blocking puppet on mysql 5.8 +- Fix service dependency when package_manage is false +- Fix selinux permissions on my.cnf + +##2015-07-23 - Supported Release 3.5.0 +###Summary +A small release to add explicit support to newer Puppet versions and accumulated patches. + +####Features/Improvements +- Start running tests against puppet 4 +- Support longer usernames on newer MariaDB versions +- Add parameters for Solaris 11 and 12 + +####Bugfixes +- Fix references to the mysql-server package +- mysql_server_id doesn't throw and error on machines without macaddress + +##2015-05-19 - Supported Release 3.4.0 +###Summary +This release includes the addition of extra facts, OpenBSD compatibility, and a number of other features, improvements and bug fixes. + +####Features/Improvements +- Added server_id fact which includes mac address for better uniqueness +- Added OpenBSD compatibility, only for 'OpenBSD -current' (due to the recent switch to mariadb) +- Added a $mysql_group parameter, and use that instead of the $root_group parameter to define the group membership of the mysql error log file. +- Updated tests for rspec-puppet 2 and future parser +- Further acceptance testing improvements +- MODULES-1928 - allow log-error to be undef +- Split package installation and database install +- README wording improvements +- Added options for including/excluding triggers and routines +- Made the 'TRIGGER' privilege of mysqldump backups depend on whether or not we are actually backing up triggers +- Cleaned up the privilege assignment in the mysqldump backup script +- Add a fact for capturing the mysql version installed + +####Bugfixes +- mysql backup: fix regression in mysql_user call +- Set service_ensure to undef, in the case of an unmanaged service +- README Typos fixed +- Bugfix on Xtrabackup crons +- Fixed a permission problem that was preventing triggers from being backed up +- MODULES-1981: Revoke and grant difference of old and new privileges +- Fix an issue were we assume triggers work +- Change default for mysql::server::backup to ignore_triggers = false + +####Deprecations +mysql::server::old_root_password property + +##2015-03-03 - Supported Release 3.3.0 +###Summary +This release includes major README updates, the addition of backup providers, and a fix for managing the log-bin directory. + +####Features +- Add package_manage parameters to `mysql::server` and `mysql::client` (MODULES-1143) +- README improvements +- Add `mysqldump`, `mysqlbackup`, and `xtrabackup` backup providers. + +####Bugfixes +- log-error overrides were not being properly used (MODULES-1804) +- check for full path for log-bin to stop puppet from managing file '.' + +##2015-02-09 - Supported Release 3.2.0 +###Summary +This release includes several new features and bugfixes, including support for various plugins, making the output from mysql_password more consistent when input is empty and improved username validation. + +####Features +- Add type and provider to manage plugins +- Add support for authentication plugins +- Add support for mysql_install_db on freebsd +- Add `create_root_user` and `create_root_my_cnf` parameters to `mysql::server` + +####Bugfixes +- Remove dependency on stdlib >= 4.1.0 (MODULES-1759) +- Make grant autorequire user +- Remove invalid parameter 'provider' from mysql_user instance (MODULES-1731) +- Return empty string for empty input in mysql_password +- Fix `mysql::account_security` when fqdn==localhost +- Update username validation (MODULES-1520) +- Future parser fix in params.pp +- Fix package name for debian 8 +- Don't start the service until the server package is installed and the config file is in place +- Test fixes +- Lint fixes + +##2014-12-16 - Supported Release 3.1.0 +###Summary + +This release includes several new features, including SLES12 support, and a number of bug fixes. + +####Notes + +`mysql::server::mysqltuner` has been refactored to fetch the mysqltuner script from github by default. If you are running on a non-network-connected system, you will need to download that file and have it available to your node at a path specified by the `source` parameter to the `mysqltuner` class. + +####Features +- Add support for install_options for all package resources (MODULES-1484) +- Add log-bin directory creation +- Allow mysql::db to import multiple files (MODULES-1338) +- SLES12 support +- Improved identifier quoting detections +- Reworked `mysql::server::mysqltuner` so that we are no longer packaging the script as it is licensed under the GPL. + +####Bugfixes +- Fix regression in username validation +- Proper containment for mysql::client in mysql::db +- Support quoted usernames of length 15 and 16 chars + +##2014-11-11 - Supported Release 3.0.0 +###Summary + +Added several new features including MariaDB support and future parser + +####Backwards-incompatible Changes +* Remove the deprecated `database`, `database_user`, and `database_grant` resources. The correct resources to use are `mysql`, `mysql_user`, and `mysql_grant` respectively. + +####Features +* Add MariaDB Support +* The mysqltuner perl script has been updated to 1.3.0 based on work at http://github.com/major/MySQLTuner-perl +* Add future parse support, fixed issues with undef to empty string +* Pass the backup credentials to 'SHOW DATABASES' +* Ability to specify the Includedir for `mysql::server` +* `mysql::db` now has an import\_timeout feature that defaults to 300 +* The `mysql` class has been removed +* `mysql::server` now takes an `override_options` hash that will affect the installation +* Ability to install both dev and client dev + +####BugFix +* `mysql::server::backup` now passes `ensure` param to the nested `mysql_grant` +* `mysql::server::service` now properly requires the presence of the `log_error` file +* `mysql::config` now occurs before `mysql::server::install_db` correctly + +##2014-07-15 - Supported Release 2.3.1 +###Summary + +This release merely updates metadata.json so the module can be uninstalled and +upgraded via the puppet module command. + +##2014-05-14 - Supported Release 2.3.0 + +This release primarily adds support for RHEL7 and Ubuntu 14.04 but it +also adds a couple of new parameters to allow for further customization, +as well as ensuring backups can backup stored procedures properly. + +####Features +Added `execpath` to allow a custom executable path for non-standard mysql installations. +Added `dbname` to mysql::db and use ensure_resource to create the resource. +Added support for RHEL7 and Fedora Rawhide. +Added support for Ubuntu 14.04. +Create a warning for if you disable SSL. +Ensure the error logfile is owned by MySQL. +Disable ssl on FreeBSD. +Add PROCESS privilege for backups. + +####Bugfixes + +####Known Bugs +* No known bugs + +##2014-03-04 - Supported Release 2.2.3 +###Summary + +This is a supported release. This release removes a testing symlink that can +cause trouble on systems where /var is on a seperate filesystem from the +modulepath. + +####Features +####Bugfixes +####Known Bugs +* No known bugs + +##2014-03-04 - Supported Release 2.2.2 +###Summary +This is a supported release. Mostly comprised of enhanced testing, plus a +bugfix for Suse. + +####Bugfixes +- PHP bindings on Suse +- Test fixes + +####Known Bugs +* No known bugs + +##2014-02-19 - Version 2.2.1 + +###Summary + +Minor release that repairs mysql_database{} so that it sees the correct +collation settings (it was only checking the global mysql ones, not the +actual database and constantly setting it over and over since January 22nd). + +Also fixes a bunch of tests on various platforms. + + +##2014-02-13 - Version 2.2.0 + +###Summary + +####Features +- Add `backupdirmode`, `backupdirowner`, `backupdirgroup` to + mysql::server::backup to allow customizing the mysqlbackupdir. +- Support multiple options of the same name, allowing you to + do 'replicate-do-db' => ['base1', 'base2', 'base3'] in order to get three + lines of replicate-do-db = base1, replicate-do-db = base2 etc. + +####Bugfixes +- Fix `restart` so it actually stops mysql restarting if set to false. +- DRY out the defaults_file functionality in the providers. +- mysql_grant fixed to work with root@localhost/@. +- mysql_grant fixed for WITH MAX_QUERIES_PER_HOUR +- mysql_grant fixed so revoking all privileges accounts for GRANT OPTION +- mysql_grant fixed to remove duplicate privileges. +- mysql_grant fixed to handle PROCEDURES when removing privileges. +- mysql_database won't try to create existing databases, breaking replication. +- bind_address renamed bind-address in 'mysqld' options. +- key_buffer renamed to key_buffer_size. +- log_error renamed to log-error. +- pid_file renamed to pid-file. +- Ensure mysql::server:root_password runs before mysql::server::backup +- Fix options_override -> override_options in the README. +- Extensively rewrite the README to be accurate and awesome. +- Move to requiring stdlib 3.2.0, shipped in PE3.0 +- Add many new tests. + + +##2013-11-13 - Version 2.1.0 + +###Summary + +The most important changes in 2.1.0 are improvements to the my.cnf creation, +as well as providers. Setting options to = true strips them to be just the +key name itself, which is required for some options. + +The provider updates fix a number of bugs, from lowercase privileges to +deprecation warnings. + +Last, the new hiera integration functionality should make it easier to +externalize all your grants, users, and, databases. Another great set of +community submissions helped to make this release. + +####Features +- Some options can not take a argument. Gets rid of the '= true' when an +option is set to true. +- Easier hiera integration: Add hash parameters to mysql::server to allow +specifying grants, users, and databases. + +####Bugfixes +- Fix an issue with lowercase privileges in mysql_grant{} causing them to be reapplied needlessly. +- Changed defaults-file to defaults-extra-file in providers. +- Ensure /root/.my.cnf is 0600 and root owned. +- database_user deprecation warning was incorrect. +- Add anchor pattern for client.pp +- Documentation improvements. +- Various test fixes. + + +##2013-10-21 - Version 2.0.1 + +###Summary + +This is a bugfix release to handle an issue where unsorted mysql_grant{} +privileges could cause Puppet to incorrectly reapply the permissions on +each run. + +####Bugfixes +- Mysql_grant now sorts privileges in the type and provider for comparison. +- Comment and test tweak for PE3.1. + + +##2013-10-14 - Version 2.0.0 + +###Summary + +(Previously detailed in the changelog for 2.0.0-rc1) + +This module has been completely refactored and works significantly different. +The changes are broad and touch almost every piece of the module. + +See the README.md for full details of all changes and syntax. +Please remain on 1.0.0 if you don't have time to fully test this in dev. + +* mysql::server, mysql::client, and mysql::bindings are the primary interface +classes. +* mysql::server takes an `override_options` parameter to set my.cnf options, +with the hash format: { 'section' => { 'thing' => 'value' }} +* mysql attempts backwards compatibility by forwarding all parameters to +mysql::server. + + +##2013-10-09 - Version 2.0.0-rc5 + +###Summary + +Hopefully the final rc! Further fixes to mysql_grant (stripping out the +cleverness so we match a much wider range of input.) + +####Bugfixes +- Make mysql_grant accept '.*'@'.*' in terms of input for user@host. + + +##2013-10-09 - Version 2.0.0-rc4 + +###Summary + +Bugfixes to mysql_grant and mysql_user form the bulk of this rc, as well as +ensuring that values in the override_options hash that contain a value of '' +are created as just "key" in the conf rather than "key =" or "key = false". + +####Bugfixes +- Improve mysql_grant to work with IPv6 addresses (both long and short). +- Ensure @host users work as well as user@host users. +- Updated my.cnf template to support items with no values. + + +##2013-10-07 - Version 2.0.0-rc3 + +###Summary +Fix mysql::server::monitor's use of mysql_user{}. + +####Bugfixes +- Fix myql::server::monitor's use of mysql_user{} to grant the proper +permissions. Add specs as well. (Thanks to treydock!) + + +##2013-10-03 - Version 2.0.0-rc2 + +###Summary +Bugfixes + +####Bugfixes +- Fix a duplicate parameter in mysql::server + + +##2013-10-03 - Version 2.0.0-rc1 + +###Summary + +This module has been completely refactored and works significantly different. +The changes are broad and touch almost every piece of the module. + +See the README.md for full details of all changes and syntax. +Please remain on 1.0.0 if you don't have time to fully test this in dev. + +* mysql::server, mysql::client, and mysql::bindings are the primary interface +classes. +* mysql::server takes an `override_options` parameter to set my.cnf options, +with the hash format: { 'section' => { 'thing' => 'value' }} +* mysql attempts backwards compatibility by forwarding all parameters to +mysql::server. + +--- +##2013-09-23 - Version 1.0.0 + +###Summary + +This release introduces a number of new type/providers, to eventually +replace the database_ ones. The module has been converted to call the +new providers rather than the previous ones as they have a number of +fixes, additional options, and work with puppet resource. + +This 1.0.0 release precedes a large refactoring that will be released +almost immediately after as 2.0.0. + +####Features +- Added mysql_grant, mysql_database, and mysql_user. +- Add `mysql::bindings` class and refactor all other bindings to be contained underneath mysql::bindings:: namespace. +- Added support to back up specified databases only with 'mysqlbackup' parameter. +- Add option to mysql::backup to set the backup script to perform a mysqldump on each database to its own file + +####Bugfixes +- Update my.cnf.pass.erb to allow custom socket support +- Add environment variable for .my.cnf in mysql::db. +- Add HOME environment variable for .my.cnf to mysqladmin command when +(re)setting root password + +--- +##2013-07-15 - Version 0.9.0 +####Features +- Add `mysql::backup::backuprotate` parameter +- Add `mysql::backup::delete_before_dump` parameter +- Add `max_user_connections` attribute to `database_user` type + +####Bugfixes +- Add client package dependency for `mysql::db` +- Remove duplicate `expire_logs_days` and `max_binlog_size` settings +- Make root's `.my.cnf` file path dynamic +- Update pidfile path for Suse variants +- Fixes for lint + +##2013-07-05 - Version 0.8.1 +####Bugfixes + - Fix a typo in the Fedora 19 support. + +##2013-07-01 - Version 0.8.0 +####Features + - mysql::perl class to install perl-DBD-mysql. + - minor improvements to the providers to improve reliability + - Install the MariaDB packages on Fedora 19 instead of MySQL. + - Add new `mysql` class parameters: + - `max_connections`: The maximum number of allowed connections. + - `manage_config_file`: Opt out of puppetized control of my.cnf. + - `ft_min_word_len`: Fine tune the full text search. + - `ft_max_word_len`: Fine tune the full text search. + - Add new `mysql` class performance tuning parameters: + - `key_buffer` + - `thread_stack` + - `thread_cache_size` + - `myisam-recover` + - `query_cache_limit` + - `query_cache_size` + - `max_connections` + - `tmp_table_size` + - `table_open_cache` + - `long_query_time` + - Add new `mysql` class replication parameters: + - `server_id` + - `sql_log_bin` + - `log_bin` + - `max_binlog_size` + - `binlog_do_db` + - `expire_logs_days` + - `log_bin_trust_function_creators` + - `replicate_ignore_table` + - `replicate_wild_do_table` + - `replicate_wild_ignore_table` + - `expire_logs_days` + - `max_binlog_size` + +####Bugfixes + - No longer restart MySQL when /root/.my.cnf changes. + - Ensure mysql::config runs before any mysql::db defines. + +##2013-06-26 - Version 0.7.1 +####Bugfixes +- Single-quote password for special characters +- Update travis testing for puppet 3.2.x and missing Bundler gems + +##2013-06-25 - Version 0.7.0 +This is a maintenance release for community bugfixes and exposing +configuration variables. + +* Add new `mysql` class parameters: + - `basedir`: The base directory mysql uses + - `bind_address`: The IP mysql binds to + - `client_package_name`: The name of the mysql client package + - `config_file`: The location of the server config file + - `config_template`: The template to use to generate my.cnf + - `datadir`: The directory MySQL's datafiles are stored + - `default_engine`: The default engine to use for tables + - `etc_root_password`: Whether or not to add the mysql root password to + /etc/my.cnf + - `java_package_name`: The name of the java package containing the java + connector + - `log_error`: Where to log errors + - `manage_service`: Boolean dictating if mysql::server should manage the + service + - `max_allowed_packet`: Maximum network packet size mysqld will accept + - `old_root_password`: Previous root user password + - `php_package_name`: The name of the phpmysql package to install + - `pidfile`: The location mysql will expect the pidfile to be + - `port`: The port mysql listens on + - `purge_conf_dir`: Value fed to recurse and purge parameters of the + /etc/mysql/conf.d resource + - `python_package_name`: The name of the python mysql package to install + - `restart`: Whether to restart mysqld + - `root_group`: Use specified group for root-owned files + - `root_password`: The root MySQL password to use + - `ruby_package_name`: The name of the ruby mysql package to install + - `ruby_package_provider`: The installation suite to use when installing the + ruby package + - `server_package_name`: The name of the server package to install + - `service_name`: The name of the service to start + - `service_provider`: The name of the service provider + - `socket`: The location of the MySQL server socket file + - `ssl_ca`: The location of the SSL CA Cert + - `ssl_cert`: The location of the SSL Certificate to use + - `ssl_key`: The SSL key to use + - `ssl`: Whether or not to enable ssl + - `tmpdir`: The directory MySQL's tmpfiles are stored +* Deprecate `mysql::package_name` parameter in favor of +`mysql::client_package_name` +* Fix local variable template deprecation +* Fix dependency ordering in `mysql::db` +* Fix ANSI quoting in queries +* Fix travis support (but still messy) +* Fix typos + +##2013-01-11 - Version 0.6.1 +* Fix providers when /root/.my.cnf is absent + +##2013-01-09 - Version 0.6.0 +* Add `mysql::server::config` define for specific config directives +* Add `mysql::php` class for php support +* Add `backupcompress` parameter to `mysql::backup` +* Add `restart` parameter to `mysql::config` +* Add `purge_conf_dir` parameter to `mysql::config` +* Add `manage_service` parameter to `mysql::server` +* Add syslog logging support via the `log_error` parameter +* Add initial SuSE support +* Fix remove non-localhost root user when fqdn != hostname +* Fix dependency in `mysql::server::monitor` +* Fix .my.cnf path for root user and root password +* Fix ipv6 support for users +* Fix / update various spec tests +* Fix typos +* Fix lint warnings + +##2012-08-23 - Version 0.5.0 +* Add puppetlabs/stdlib as requirement +* Add validation for mysql privs in provider +* Add `pidfile` parameter to mysql::config +* Add `ensure` parameter to mysql::db +* Add Amazon linux support +* Change `bind_address` parameter to be optional in my.cnf template +* Fix quoting root passwords + +##2012-07-24 - Version 0.4.0 +* Fix various bugs regarding database names +* FreeBSD support +* Allow specifying the storage engine +* Add a backup class +* Add a security class to purge default accounts + +##2012-05-03 - Version 0.3.0 +* 14218 Query the database for available privileges +* Add mysql::java class for java connector installation +* Use correct error log location on different distros +* Fix set_mysql_rootpw to properly depend on my.cnf + +##2012-04-11 - Version 0.2.0 + +##2012-03-19 - William Van Hevelingen +* (#13203) Add ssl support (f7e0ea5) + +##2012-03-18 - Nan Liu +* Travis ci before script needs success exit code. (0ea463b) + +##2012-03-18 - Nan Liu +* Fix Puppet 2.6 compilation issues. (9ebbbc4) + +##2012-03-16 - Nan Liu +* Add travis.ci for testing multiple puppet versions. (33c72ef) + +##2012-03-15 - William Van Hevelingen +* (#13163) Datadir should be configurable (f353fc6) + +##2012-03-16 - Nan Liu +* Document create_resources dependency. (558a59c) + +##2012-03-16 - Nan Liu +* Fix spec test issues related to error message. (eff79b5) + +##2012-03-16 - Nan Liu +* Fix mysql service on Ubuntu. (72da2c5) + +##2012-03-16 - Dan Bode +* Add more spec test coverage (55e399d) + +##2012-03-16 - Nan Liu +* (#11963) Fix spec test due to path changes. (1700349) + +##2012-03-07 - François Charlier +* Add a test to check path for 'mysqld-restart' (b14c7d1) + +##2012-03-07 - François Charlier +* Fix path for 'mysqld-restart' (1a9ae6b) + +##2012-03-15 - Dan Bode +* Add rspec-puppet tests for mysql::config (907331a) + +##2012-03-15 - Dan Bode +* Moved class dependency between sever and config to server (da62ad6) + +##2012-03-14 - Dan Bode +* Notify mysql restart from set_mysql_rootpw exec (0832a2c) + +##2012-03-15 - Nan Liu +* Add documentation related to osfamily fact. (8265d28) + +##2012-03-14 - Dan Bode +* Mention osfamily value in failure message (e472d3b) + +##2012-03-14 - Dan Bode +* Fix bug when querying for all database users (015490c) + +##2012-02-09 - Nan Liu +* Major refactor of mysql module. (b1f90fd) + +##2012-01-11 - Justin Ellison +* Ruby and Python's MySQL libraries are named differently on different distros. (1e926b4) + +##2012-01-11 - Justin Ellison +* Per @ghoneycutt, we should fail explicitly and explain why. (09af083) + +##2012-01-11 - Justin Ellison +* Removing duplicate declaration (7513d03) + +##2012-01-10 - Justin Ellison +* Use socket value from params class instead of hardcoding. (663e97c) + +##2012-01-10 - Justin Ellison +* Instead of hardcoding the config file target, pull it from mysql::params (031a47d) + +##2012-01-10 - Justin Ellison +* Moved $socket to within the case to toggle between distros. Added a $config_file variable to allow per-distro config file destinations. (360eacd) + +##2012-01-10 - Justin Ellison +* Pretty sure this is a bug, 99% of Linux distros out there won't ever hit the default. (3462e6b) + +##2012-02-09 - William Van Hevelingen +* Changed the README to use markdown (3b7dfeb) + +##2012-02-04 - Daniel Black +* (#12412) mysqltuner.pl update (b809e6f) + +##2011-11-17 - Matthias Pigulla +* (#11363) Add two missing privileges to grant: event_priv, trigger_priv (d15c9d1) + +##2011-12-20 - Jeff McCune +* (minor) Fixup typos in Modulefile metadata (a0ed6a1) + +##2011-12-19 - Carl Caum +* Only notify Exec to import sql if sql is given (0783c74) + +##2011-12-19 - Carl Caum +* (#11508) Only load sql_scripts on DB creation (e3b9fd9) + +##2011-12-13 - Justin Ellison +* Require not needed due to implicit dependencies (3058feb) + +##2011-12-13 - Justin Ellison +* Bug #11375: puppetlabs-mysql fails on CentOS/RHEL (a557b8d) + +##2011-06-03 - Dan Bode - 0.0.1 +* initial commit diff --git a/environments/prod/modules/mysql/CONTRIBUTING.md b/environments/prod/modules/mysql/CONTRIBUTING.md new file mode 100644 index 0000000..3c3f1e7 --- /dev/null +++ b/environments/prod/modules/mysql/CONTRIBUTING.md @@ -0,0 +1,218 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% bundle exec rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% bundle exec rake spec_clean +% bundle exec rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppet.com/community/get-help) + +* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) + diff --git a/environments/prod/modules/mysql/Gemfile b/environments/prod/modules/mysql/Gemfile new file mode 100644 index 0000000..e8b3fa6 --- /dev/null +++ b/environments/prod/modules/mysql/Gemfile @@ -0,0 +1,52 @@ +#This file is generated by ModuleSync, do not edit. + +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +def location_from_env(env, default_location = []) + if location = ENV[env] + if location =~ /^((?:git|https?)[:@][^#]*)#(.*)/ + [{ :git => $1, :branch => $2, :require => false }] + elsif location =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [location, { :require => false }] + end + else + default_location + end +end + +group :development, :unit_tests do + gem 'metadata-json-lint' + gem 'puppet_facts' + gem 'puppet-blacksmith', '>= 3.4.0' + gem 'puppetlabs_spec_helper', '>= 1.2.1' + gem 'rspec-puppet', '>= 2.3.2' + gem 'rspec-puppet-facts' + gem 'mocha', '< 1.2.0' + gem 'simplecov' + gem 'parallel_tests', '< 2.10.0' if RUBY_VERSION < '2.0.0' + gem 'parallel_tests' if RUBY_VERSION >= '2.0.0' + gem 'rubocop', '0.41.2' if RUBY_VERSION < '2.0.0' + gem 'rubocop' if RUBY_VERSION >= '2.0.0' + gem 'rubocop-rspec', '~> 1.6' if RUBY_VERSION >= '2.3.0' + gem 'json_pure', '<= 2.0.1' if RUBY_VERSION < '2.0.0' + gem 'rspec-puppet-facts' +end +group :system_tests do + gem 'beaker', *location_from_env('BEAKER_VERSION', []) if RUBY_VERSION >= '2.3.0' + gem 'beaker', *location_from_env('BEAKER_VERSION', ['< 3']) if RUBY_VERSION < '2.3.0' + gem 'beaker-pe' if RUBY_VERSION >= '2.3.0' + gem 'beaker-rspec', *location_from_env('BEAKER_RSPEC_VERSION', ['>= 3.4']) + gem 'serverspec' + gem 'beaker-puppet_install_helper' + gem 'master_manipulator' + gem 'beaker-hostgenerator', *location_from_env('BEAKER_HOSTGENERATOR_VERSION', []) +end + +gem 'facter', *location_from_env('FACTER_GEM_VERSION') +gem 'puppet', *location_from_env('PUPPET_GEM_VERSION') + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end diff --git a/environments/prod/modules/mysql/LICENSE b/environments/prod/modules/mysql/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/environments/prod/modules/mysql/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/environments/prod/modules/mysql/NOTICE b/environments/prod/modules/mysql/NOTICE new file mode 100644 index 0000000..caaa8f4 --- /dev/null +++ b/environments/prod/modules/mysql/NOTICE @@ -0,0 +1,18 @@ +mysql puppet module + +Copyright (C) 2013-2016 Puppet Labs, Inc. + +Puppet Labs can be contacted at: info@puppetlabs.com + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/environments/prod/modules/mysql/README.md b/environments/prod/modules/mysql/README.md new file mode 100644 index 0000000..290d240 --- /dev/null +++ b/environments/prod/modules/mysql/README.md @@ -0,0 +1,1050 @@ +# mysql + +#### Table of Contents + +1. [Module Description - What the module does and why it is useful](#module-description) +2. [Setup - The basics of getting started with mysql](#setup) + * [Beginning with mysql](#beginning-with-mysql) +3. [Usage - Configuration options and additional functionality](#usage) + * [Customize server options](#customize-server-options) + * [Create a database](#create-a-database) + * [Customize configuration](#create-custom-configuration) + * [Work with an existing server](#work-with-an-existing-server) + * [Specify passwords](#specify-passwords) + * [Install Percona server on CentOS](#install-percona-server-on-centos) + * [Install MariaDB on Ubuntu](#install-mariadb-on-ubuntu) +4. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +5. [Limitations - OS compatibility, etc.](#limitations) +6. [Development - Guide for contributing to the module](#development) + +## Module Description + +The mysql module installs, configures, and manages the MySQL service. + +This module manages both the installation and configuration of MySQL, as well as extending Puppet to allow management of MySQL resources, such as databases, users, and grants. + +## Setup + +### Beginning with mysql + +To install a server with the default options: + +`include '::mysql::server'`. + +To customize options, such as the root +password or `/etc/my.cnf` settings, you must also pass in an override hash: + +```puppet +class { '::mysql::server': + root_password => 'strongpassword', + remove_default_accounts => true, + override_options => $override_options +} +``` + +See [**Customize Server Options**](#customize-server-options) below for examples of the hash structure for $override_options`. + +## Usage + +All interaction for the server is done via `mysql::server`. To install the client, use `mysql::client`. To install bindings, use `mysql::bindings`. + +### Customize server options + +To define server options, structure a hash structure of overrides in `mysql::server`. This hash resembles a hash in the my.cnf file: + +```puppet +$override_options = { + 'section' => { + 'item' => 'thing', + } +} +``` + +For options that you would traditionally represent in this format: + +``` +[section] +thing = X +``` + +...you can make an entry like `thing => true`, `thing => value`, or `thing => "` in the hash. Alternatively, you can pass an array, as `thing => ['value', 'value2']`, or list each `thing => value` separately on separate lines. + +You can pass a variable in the hash without setting a value for it; the variable would then use MySQL's default settings. To exclude an option from the my.cnf file --- for example, when using `override_options` to revert to a default value --- pass `thing => undef`. + +If an option needs multiple instances, pass an array. For example, + +```puppet +$override_options = { + 'mysqld' => { + 'replicate-do-db' => ['base1', 'base2'], + } +} +``` + +produces + +``` +[mysqld] +replicate-do-db = base1 +replicate-do-db = base2 +``` + +To implement version specific parameters, specify the version, such as [mysqld-5.5]. This allows one config for different versions of MySQL. + +### Create a database + +To create a database with a user and some assigned privileges: + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +``` + +To use a different resource name with exported resources: + +```puppet + @@mysql::db { "mydb_${fqdn}": + user => 'myuser', + password => 'mypass', + dbname => 'mydb', + host => ${fqdn}, + grant => ['SELECT', 'UPDATE'], + tag => $domain, +} +``` + +Then you can collect it on the remote DB server: + +```puppet +Mysql::Db <<| tag == $domain |>> +``` + +If you set the sql parameter to a file when creating a database, the file is imported into the new database. + +For large sql files, increase the `import_timeout` parameter, which defaults to 300 seconds. + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], + sql => '/path/to/sqlfile.gz', + import_cat_cmd => 'zcat', + import_timeout => 900, +} +``` + +### Customize configuration + +To add custom MySQL configuration, place additional files into `includedir`. This allows you to override settings or add additional ones, which is helpful if you don't use `override_options` in `mysql::server`. The `includedir` location is by default set to `/etc/mysql/conf.d`. + +### Work with an existing server + +To instantiate databases and users on an existing MySQL server, you need a `.my.cnf` file in `root`'s home directory. This file must specify the remote server address and credentials. For example: + +``` +[client] +user=root +host=localhost +password=secret +``` + +This module uses the `mysqld_version` fact to discover the server version being used. By default, this is set to the output of `mysqld -V`. If you're working with a remote MySQL server, you may need to set a custom fact for `mysqld_version` to ensure correct behaviour. + +When working with a remote server, do *not* use the `mysql::server` class in your Puppet manifests. + +### Specify passwords + +In addition to passing passwords as plain text, you can input them as hashes. For example: + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +``` + +### Install Percona server on CentOS + +This example shows how to do a minimal installation of a Percona server on a +CentOS system. This sets up the Percona server, client, and bindings (including Perl and Python bindings). You can customize this usage and update the version as needed. + +This usage has been tested on Puppet 4.4 / CentOS 7 / Percona Server 5.7. + +**Note:** The installation of the yum repository is not part of this package +and is here only to show a full example of how you can install. + +```puppet +yumrepo { 'percona': + descr => 'CentOS $releasever - Percona', + baseurl => 'http://repo.percona.com/centos/$releasever/os/$basearch/', + gpgkey => 'http://www.percona.com/downloads/percona-release/RPM-GPG-KEY-percona', + enabled => 1, + gpgcheck => 1, +} + +class {'mysql::server': + package_name => 'Percona-Server-server-57', + package_ensure => '5.7.11-4.1.el7', + service_name => 'mysql', + config_file => '/etc/my.cnf', + includedir => '/etc/my.cnf.d', + root_password => 'PutYourOwnPwdHere', + override_options => { + mysqld => { + log-error => '/var/log/mysqld.log', + pid-file => '/var/run/mysqld/mysqld.pid', + }, + mysqld_safe => { + log-error => '/var/log/mysqld.log', + }, + } +} + +# Note: Installing Percona-Server-server-57 also installs Percona-Server-client-57. +# This shows how to install the Percona MySQL client on its own +class {'mysql::client': + package_name => 'Percona-Server-client-57', + package_ensure => '5.7.11-4.1.el7', +} + +# These packages are normally installed along with Percona-Server-server-57 +# If you needed to install the bindings, however, you could do so with this code +class { 'mysql::bindings': + client_dev_package_name => 'Percona-Server-shared-57', + client_dev_package_ensure => '5.7.11-4.1.el7', + client_dev => true, + daemon_dev_package_name => 'Percona-Server-devel-57', + daemon_dev_package_ensure => '5.7.11-4.1.el7', + daemon_dev => true, + perl_enable => true, + perl_package_name => 'perl-DBD-MySQL', + python_enable => true, + python_package_name => 'MySQL-python', +} + +# Dependencies definition +Yumrepo['percona']-> +Class['mysql::server'] + +Yumrepo['percona']-> +Class['mysql::client'] + +Yumrepo['percona']-> +Class['mysql::bindings'] +``` + +### Install MariaDB on Ubuntu + +#### Optional: Install the MariaDB official repo + +In this example, we'll use the latest stable (currently 10.1) from the official MariaDB repository, not the one from the distro repository. You could instead use the package from the Ubuntu repository. Make sure you use the repository corresponding to the version you want. + +**Note:** `sfo1.mirrors.digitalocean.com` is one of many mirrors available. You can use any official mirror. + +``` +include apt + +apt::source { 'mariadb': + location => 'http://sfo1.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu', + release => $::lsbdistcodename, + repos => 'main', + key => { + id => '199369E5404BD5FC7D2FE43BCBCB082A1BB943DB', + server => 'hkp://keyserver.ubuntu.com:80', + }, + include => { + src => false, + deb => true, + }, +} +``` + +#### Install the MariaDB server + +This example shows MariaDB server installation on Ubuntu Trusty. Adjust the version and the parameters of `my.cnf` as needed. All parameters of the `my.cnf` can be defined using the `override_options` parameter. + +The folders `/var/log/mysql` and `/var/run/mysqld` are created automatically, but if you are using other custom folders, they should exist as prerequisites for this code. + +All the values set here are an example of a working minimal configuration. + +Specify the version of the package you want with the `package_ensure` parameter. + +``` +class {'::mysql::server': + package_name => 'mariadb-server', + package_ensure => '10.1.14+maria-1~trusty', + service_name => 'mysql', + root_password => 'AVeryStrongPasswordUShouldEncrypt!', + override_options => { + mysqld => { + 'log-error' => '/var/log/mysql/mariadb.log', + 'pid-file' => '/var/run/mysqld/mysqld.pid', + }, + mysqld_safe => { + 'log-error' => '/var/log/mysql/mariadb.log', + }, + } +} + +# Dependency management. Only use that part if you are installing the repository +# as shown in the Preliminary step of this example. +Apt::Source['mariadb'] ~> +Class['apt::update'] -> +Class['::mysql::server'] + +``` + +#### Install the MariaDB client + +This example shows how to install the MariaDB client and all of the bindings at once. You can do this installation separately from the server installation. + +Specify the version of the package you want with the `package_ensure` parameter. + +``` +class {'::mysql::client': + package_name => 'mariadb-client', + package_ensure => '10.1.14+maria-1~trusty', + bindings_enable => true, +} + +# Dependency management. Only use that part if you are installing the repository +# as shown in the Preliminary step of this example. +Apt::Source['mariadb'] ~> +Class['apt::update'] -> +Class['::mysql::client'] +``` + +## Reference + +### Classes + +#### Public classes + +* [`mysql::server`](#mysqlserver): Installs and configures MySQL. +* [`mysql::server::monitor`](#mysqlservermonitor): Sets up a monitoring user. +* [`mysql::server::mysqltuner`](#mysqlservermysqltuner): Installs MySQL tuner script. +* [`mysql::server::backup`](#mysqlserverbackup): Sets up MySQL backups via cron. +* [`mysql::bindings`](#mysqlbindings): Installs various MySQL language bindings. +* [`mysql::client`](#mysqlclient): Installs MySQL client (for non-servers). + +#### Private classes + +* `mysql::server::install`: Installs packages. +* `mysql::server::installdb`: Implements setup of mysqld data directory (e.g. /var/lib/mysql) +* `mysql::server::config`: Configures MYSQL. +* `mysql::server::service`: Manages service. +* `mysql::server::account_security`: Deletes default MySQL accounts. +* `mysql::server::root_password`: Sets MySQL root password. +* `mysql::server::providers`: Creates users, grants, and databases. +* `mysql::bindings::client_dev`: Installs MySQL client development package. +* `mysql::bindings::daemon_dev`: Installs MySQL daemon development package. +* `mysql::bindings::java`: Installs Java bindings. +* `mysql::bindings::perl`: Installs Perl bindings. +* `mysql::bindings::php`: Installs PHP bindings. +* `mysql::bindings::python`: Installs Python bindings. +* `mysql::bindings::ruby`: Installs Ruby bindings. +* `mysql::client::install`: Installs MySQL client. +* `mysql::backup::mysqldump`: Implements mysqldump backups. +* `mysql::backup::mysqlbackup`: Implements backups with Oracle MySQL Enterprise Backup. +* `mysql::backup::xtrabackup`: Implements backups with XtraBackup from Percona. + +### Parameters + +#### mysql::server + +##### `create_root_user` + +Whether root user should be created. Valid values are true, false. Defaults to true. + +This is useful for a cluster setup with Galera. The root user has to be created only once. You can set this parameter true on one node and set it to false on the remaining nodes. + +##### `create_root_my_cnf` + +Whether to create `/root/.my.cnf`. Valid values are true, false. Defaults to true. + +`create_root_my_cnf` allows creation of `/root/.my.cnf` independently of `create_root_user`. You can use this for a cluster setup with Galera where you want `/root/.my.cnf` to exist on all nodes. + +##### `root_password` + +The MySQL root password. Puppet attempts to set the root password and update `/root/.my.cnf` with it. + +This is required if `create_root_user` or `create_root_my_cnf` are true. If `root_password` is 'UNSET', then `create_root_user` and `create_root_my_cnf` are assumed to be false --- that is, the MySQL root user and `/root/.my.cnf` are not created. + +Password changes are supported; however, the old password must be set in `/root/.my.cnf`. Effectively, Puppet uses the old password, configured in `/root/my.cnf`, to set the new password in MySQL, and then updates `/root/.my.cnf` with the new password. + +##### `old_root_password` + +This parameter no longer does anything. It exists only for backwards compatibility. See the `root_password` parameter above for details on changing the root password. + +##### `override_options` + +Specifies override options to pass into MySQL. Structured like a hash in the my.cnf file: + +```puppet +$override_options = { + 'section' => { + 'item' => 'thing', + } +} +``` + +See [**Customize Server Options**](#customize-server-options) above for usage details. + +##### `config_file` + +The location, as a path, of the MySQL configuration file. + +##### `manage_config_file` + +Whether the MySQL configuration file should be managed. Valid values are true, false. Defaults to true. + +##### `includedir` +The location, as a path, of !includedir for custom configuration overrides. + +##### `install_options` +Passes [install_options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) array to managed package resources. You must pass the appropriate options for the specified package manager. + +##### `purge_conf_dir` + +Whether the `includedir` directory should be purged. Valid values are true, false. Defaults to false. + +##### `restart` + +Whether the service should be restarted when things change. Valid values are true, false. Defaults to false. + +##### `root_group` + +The name of the group used for root. Can be a group name or a group ID. See more about the [`group` file attribute](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). + +##### `mysql_group` + +The name of the group of the MySQL daemon user. Can be a group name or a group ID. See more about the [`group` file attribute](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). + +##### `package_ensure` + +Whether the package exists or should be a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Defaults to 'present'. + +##### `package_manage` + +Whether to manage the MySQL server package. Defaults to true. + +##### `package_name` + +The name of the MySQL server package to install. + +##### `remove_default_accounts` + +Specifies whether to automatically include `mysql::server::account_security`. Valid values are true, false. Defaults to false. + +##### `service_enabled` + +Specifies whether the service should be enabled. Valid values are true, false. Defaults to true. + +##### `service_manage` + +Specifies whether the service should be managed. Valid values are true, false. Defaults to true. + +##### `service_name` + +The name of the MySQL server service. Defaults are OS dependent, defined in params.pp. + +##### `service_provider` + +The provider to use to manage the service. For Ubuntu, defaults to 'upstart'; otherwise, default is undefined. + +##### `users` + +Optional hash of users to create, which are passed to [mysql_user](#mysql_user). + +``` +users => { + 'someuser@localhost' => { + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', + password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', + tls_options => ['NONE'], + }, +} +``` + +##### `grants` + +Optional hash of grants, which are passed to [mysql_grant](#mysql_grant). + +``` +grants => { + 'someuser@localhost/somedb.*' => { + ensure => 'present', + options => ['GRANT'], + privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], + table => 'somedb.*', + user => 'someuser@localhost', + }, +} +``` + +##### `databases` + +Optional hash of databases to create, which are passed to [mysql_database](#mysql_database). + +``` +databases => { + 'somedb' => { + ensure => 'present', + charset => 'utf8', + }, +} +``` + +#### mysql::server::backup + +##### `backupuser` + +MySQL user to create for backups. + +##### `backuppassword` + +MySQL user password for backups. + +##### `backupdir` + +Directory in which to store backups. + +##### `backupdirmode` + +Permissions applied to the backup directory. This parameter is passed directly +to the `file` resource. + +##### `backupdirowner` + +Owner for the backup directory. This parameter is passed directly to the `file` +resource. + +##### `backupdirgroup` + +Group owner for the backup directory. This parameter is passed directly to the +`file` resource. + +##### `backupcompress` + +Whether backups should be compressed. Valid values are true, false. Defaults to true. + +##### `backuprotate` + +How many days to keep backups. Valid value is an integer. Defaults to '30'. + +##### `delete_before_dump` + +Whether to delete old .sql files before backing up. Setting to true deletes old files before backing up, while setting to false deletes them after backup. Valid values are true, false. Defaults to false. + +##### `backupdatabases` + +Specifies an array of databases to back up. + +##### `file_per_database` + +Whether a separate file be used per database. Valid values are true, false. Defaults to false. + +##### `include_routines` + +Whether or not to include routines for each database when doing a `file_per_database` backup. Defaults to false. + +##### `include_triggers` + +Whether or not to include triggers for each database when doing a `file_per_database` backup. Defaults to false. + +##### `ensure` + +Allows you to remove the backup scripts. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `execpath` + +Allows you to set a custom PATH should your MySQL installation be non-standard places. Defaults to `/usr/bin:/usr/sbin:/bin:/sbin`. + +##### `time` + +An array of two elements to set the backup time. Allows ['23', '5'] (i.e., 23:05) or ['3', '45'] (i.e., 03:45) for HH:MM times. + +##### `postscript` + +A script that is executed when the backup is finished. This could be used to (r)sync the backup to a central store. This script can be either a single line that is directly executed or a number of lines supplied as an array. It could also be one or more externally managed (executable) files. + +##### `prescript` + +A script that is executed before the backup begins. + +##### `provider` + +Sets the server backup implementation. Valid values are: + +* `mysqldump`: Implements backups with mysqldump. Backup type: Logical. This is the default value. +* `mysqlbackup`: Implements backups with MySQL Enterprise Backup from Oracle. Backup type: Physical. To use this type of backup, you'll need the `meb` package, which is available in RPM and TAR formats from Oracle. For Ubuntu, you can use [meb-deb](https://github.com/dveeden/meb-deb) to create a package from an official tarball. +* `xtrabackup`: Implements backups with XtraBackup from Percona. Backup type: Physical. + +##### `maxallowedpacket` + +Defines the maximum SQL statement size for the backup dump script. The default value is 1MB, as this is the default MySQL Server value. + +#### mysql::server::monitor + +##### `mysql_monitor_username` + +The username to create for MySQL monitoring. + +##### `mysql_monitor_password` + +The password to create for MySQL monitoring. + +##### `mysql_monitor_hostname` + +The hostname from which the monitoring user requests are allowed access. + +#### mysql::server::mysqltuner + +**Note**: If you're using this class on a non-network-connected system, you must download the mysqltuner.pl script and have it hosted somewhere accessible via `http(s)://`, `puppet://`, `ftp://`, or a fully qualified file path. + +##### `ensure` + +Ensures that the resource exists. Valid values are `present`, `absent`. Defaults to `present`. + +##### `version` + +The version to install from the major/MySQLTuner-perl github repository. Must be a valid tag. Defaults to 'v1.3.0'. + +##### `source` + +Specifies the source. If not specified, defaults to `https://github.com/major/MySQLTuner-perl/raw/${version}/mysqltuner.pl` + +#### mysql::bindings + +##### `client_dev` + +Specifies whether `::mysql::bindings::client_dev` should be included. Valid values are true', false. Defaults to false. + +##### `daemon_dev` + +Specifies whether `::mysql::bindings::daemon_dev` should be included. Valid values are true, false. Defaults to false. + +##### `java_enable` + +Specifies whether `::mysql::bindings::java` should be included. Valid values are true, false. Defaults to false. + +##### `perl_enable` + +Specifies whether `mysql::bindings::perl` should be included. Valid values are true, false. Defaults to false. + +##### `php_enable` + +Specifies whether `mysql::bindings::php` should be included. Valid values are true, false. Defaults to false. + +##### `python_enable` + +Specifies whether `mysql::bindings::python` should be included. Valid values are true, false. Defaults to false. + +##### `ruby_enable` + +Specifies whether `mysql::bindings::ruby` should be included. Valid values are true, false. Defaults to false. + +##### `install_options` + +Passes `install_options` array to managed package resources. You must pass the [appropriate options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) for the package manager(s). + +##### `client_dev_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `client_dev => true`. + +##### `client_dev_package_name` + +The name of the client_dev package to install. Only applies if `client_dev => true`. + +##### `client_dev_package_provider` + +The provider to use to install the client_dev package. Only applies if `client_dev => true`. + +##### `daemon_dev_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `daemon_dev => true`. + +##### `daemon_dev_package_name` + +The name of the daemon_dev package to install. Only applies if `daemon_dev => true`. + +##### `daemon_dev_package_provider` + +The provider to use to install the daemon_dev package. Only applies if `daemon_dev => true`. + +##### `java_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `java_enable => true`. + +##### `java_package_name` + +The name of the Java package to install. Only applies if `java_enable => true`. + +##### `java_package_provider` + +The provider to use to install the Java package. Only applies if `java_enable => true`. + +##### `perl_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `perl_enable => true`. + +##### `perl_package_name` + +The name of the Perl package to install. Only applies if `perl_enable => true`. + +##### `perl_package_provider` + +The provider to use to install the Perl package. Only applies if `perl_enable => true`. + +##### `php_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `php_enable => true`. + +##### `php_package_name` + +The name of the PHP package to install. Only applies if `php_enable => true`. + +##### `python_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `python_enable => true`. + +##### `python_package_name` + +The name of the Python package to install. Only applies if `python_enable => true`. + +##### `python_package_provider` + +The provider to use to install the PHP package. Only applies if `python_enable => true`. + +##### `ruby_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `ruby_enable => true`. + +##### `ruby_package_name` + +The name of the Ruby package to install. Only applies if `ruby_enable => true`. + +##### `ruby_package_provider` + +What provider should be used to install the package. + +#### mysql::client + +##### `bindings_enable` + +Whether to automatically install all bindings. Valid values are true, false. Default to false. + +##### `install_options` +Array of install options for managed package resources. You must pass the appropriate options for the package manager. + +##### `package_ensure` + +Whether the MySQL package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. + +##### `package_manage` + +Whether to manage the MySQL client package. Defaults to true. + +##### `package_name` + +The name of the MySQL client package to install. + +### Defines + +#### mysql::db + +``` +mysql_database { 'information_schema': + ensure => 'present', + charset => 'utf8', + collate => 'utf8_swedish_ci', +} +mysql_database { 'mysql': + ensure => 'present', + charset => 'latin1', + collate => 'latin1_swedish_ci', +} +``` + +##### `user` + +The user for the database you're creating. + +##### `password` + +The password for $user for the database you're creating. + +##### `dbname` + +The name of the database to create. Defaults to $name. + +##### `charset` + +The character set for the database. Defaults to 'utf8'. + +##### `collate` + +The collation for the database. Defaults to 'utf8_general_ci'. + +##### `host` + +The host to use as part of user@host for grants. Defaults to 'localhost'. + +##### `grant` + +The privileges to be granted for user@host on the database. Defaults to 'ALL'. + +##### `sql` + +The path to the sqlfile you want to execute. This can be single file specified as string, or it can be an array of strings. Defaults to undef. + +##### `enforce_sql` + +Specifies whether executing the sqlfiles should happen on every run. If set to false, sqlfiles only run once. Valid values are true, false. Defaults to false. + +##### `ensure` + +Specifies whether to create the database. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `import_timeout` + +Timeout, in seconds, for loading the sqlfiles. Defaults to '300'. + +##### `import_cat_cmd` + +Command to read the sqlfile for importing the database. Useful for compressed sqlfiles. For example, you can use 'zcat' for .gz files. Defaults to 'cat'. + +### Types + +#### mysql_database + +`mysql_database` creates and manages databases within MySQL. + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +The name of the MySQL database to manage. + +##### `charset` + +The CHARACTER SET setting for the database. Defaults to ':utf8'. + +##### `collate` + +The COLLATE setting for the database. Defaults to ':utf8_general_ci'. + +#### mysql_user + +Creates and manages user grants within MySQL. + +``` +mysql_user { 'root@127.0.0.1': + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', +} +``` + +You can also specify an authentication plugin. + +``` +mysql_user{ 'myuser'@'localhost': + ensure => 'present', + plugin => 'unix_socket', +} +``` + +TLS options can be specified for a user. +``` +mysql_user{ 'myuser'@'localhost': + ensure => 'present', + tls_options => ['SSL'], +} +``` + +##### `name` + +The name of the user, as 'username@hostname' or username@hostname. + +##### `password_hash` + +The user's password hash of the user. Use mysql_password() for creating such a hash. + +##### `max_user_connections` + +Maximum concurrent connections for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_connections_per_hour` + +Maximum connections per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_queries_per_hour` + +Maximum queries per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_updates_per_hour` + +Maximum updates per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `tls_options` + +SSL-related options for a MySQL account, using one or more tls_option values. 'NONE' specifies that the account has no TLS options enforced, and the available options are 'SSL', 'X509', 'CIPHER *cipher*', 'ISSUER *issuer*', 'SUBJECT *subject*'; as stated in the MySQL documentation. + + +#### mysql_grant + +`mysql_grant` creates grant permissions to access databases within MySQL. To create grant permissions to access databases with MySQL, use it you must create the title of the resource as shown below, following the pattern of `username@hostname/database.table`: + +``` +mysql_grant { 'root@localhost/*.*': + ensure => 'present', + options => ['GRANT'], + privileges => ['ALL'], + table => '*.*', + user => 'root@localhost', +} +``` + +It is possible to specify privileges down to the column level: + +``` +mysql_grant { 'root@localhost/mysql.user': + ensure => 'present', + privileges => ['SELECT (Host, User)'], + table => 'mysql.user', + user => 'root@localhost', +} +``` + +To revoke GRANT privilege specify ['NONE']. + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +Name to describe the grant. Must in a 'user/table' format. + +##### `privileges` + +Privileges to grant the user. + +##### `table` + +The table to which privileges are applied. + +##### `user` + +User to whom privileges are granted. + +##### `options` + +MySQL options to grant. Optional. + +#### mysql_plugin + +`mysql_plugin` can be used to load plugins into the MySQL Server. + +``` +mysql_plugin { 'auth_socket': + ensure => 'present', + soname => 'auth_socket.so', +} +``` + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +The name of the MySQL plugin to manage. + +##### `soname` + +The library file name. + +#### `mysql_datadir` + +Initializes the MySQL data directory with version specific code. Pre MySQL 5.7.6 +it uses mysql_install_db. After MySQL 5.7.6 it uses mysqld --initialize-insecure. + +Insecure initialization is needed, as mysqld version 5.7 introduced "secure by default" mode. +This means MySQL generates a random password and writes it to STDOUT. This means puppet +can never accesss the database server afterwards, as no credencials are available. + +This type is an internal type and should not be called directly. + +### Facts + +#### `mysql_version` + +Determines the MySQL version by parsing the output from `mysql --version` + +#### `mysql_server_id` + +Generates a unique id, based on the node's MAC address, which can be used as +`server_id`. This fact will *always* return `0` on nodes that have only +loopback interfaces. Because those nodes aren't connected to the outside world, this shouldn't cause any conflicts. + +## Limitations + +This module has been tested on: + +* RedHat Enterprise Linux 5, 6, 7 +* Debian 6, 7, 8 +* CentOS 5, 6, 7 +* Ubuntu 10.04, 12.04, 14.04, 16.04 +* Scientific Linux 5, 6 +* SLES 11 + +Testing on other platforms has been minimal and cannot be guaranteed. + +**Note:** The mysqlbackup.sh does not work and is not supported on MySQL 5.7 and greater. + +## Development + +Puppet Labs modules on the Puppet Forge are open projects, and community +contributions are essential for keeping them great. We can't access the +huge number of platforms and myriad of hardware, software, and deployment +configurations that Puppet is intended to serve. + +We want to keep it as easy as possible to contribute changes so that our +modules work in your environment. There are a few guidelines that we need +contributors to follow so that we can have a chance of keeping on top of things. + +Check out our the complete [module contribution guide](https://docs.puppetlabs.com/forge/contributing.html). + +### Authors + +This module is based on work by David Schmitt. The following contributors have contributed to this module (beyond Puppet Labs): + +* Larry Ludwig +* Christian G. Warden +* Daniel Black +* Justin Ellison +* Lowe Schmidt +* Matthias Pigulla +* William Van Hevelingen +* Michael Arnold +* Chris Weyl +* Daniël van Eeden +* Jan-Otto Kröpke diff --git a/environments/prod/modules/mysql/Rakefile b/environments/prod/modules/mysql/Rakefile new file mode 100644 index 0000000..3e8d4cb --- /dev/null +++ b/environments/prod/modules/mysql/Rakefile @@ -0,0 +1,38 @@ +require 'puppet_blacksmith/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +require 'puppetlabs_spec_helper/rake_tasks' + +PuppetLint.configuration.send('relative') +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_single_quote_string_with_variables') + +desc 'Generate pooler nodesets' +task :gen_nodeset do + require 'beaker-hostgenerator' + require 'securerandom' + require 'fileutils' + + agent_target = ENV['TEST_TARGET'] + if ! agent_target + STDERR.puts 'TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat-64default."' + agent_target = 'redhat-64default.' + end + + master_target = ENV['MASTER_TEST_TARGET'] + if ! master_target + STDERR.puts 'MASTER_TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat7-64mdcl"' + master_target = 'redhat7-64mdcl' + end + + targets = "#{master_target}-#{agent_target}" + cli = BeakerHostGenerator::CLI.new([targets]) + nodeset_dir = "tmp/nodesets" + nodeset = "#{nodeset_dir}/#{targets}-#{SecureRandom.uuid}.yaml" + FileUtils.mkdir_p(nodeset_dir) + File.open(nodeset, 'w') do |fh| + fh.print(cli.execute) + end + puts nodeset +end diff --git a/environments/prod/modules/mysql/TODO b/environments/prod/modules/mysql/TODO new file mode 100644 index 0000000..3913293 --- /dev/null +++ b/environments/prod/modules/mysql/TODO @@ -0,0 +1,8 @@ +The best that I can tell is that this code traces back to David Schmitt. It has been forked many times since then :) + +1. you cannot add databases to an instance that has a root password +2. you have to specify username as USER@BLAH or it cannot be found +3. mysql_grant does not complain if user does not exist +4. Needs support for pre-seeding on debian +5. the types may need to take user/password +6. rather or not to configure /etc/.my.cnf should be configurable diff --git a/environments/prod/modules/mysql/checksums.json b/environments/prod/modules/mysql/checksums.json new file mode 100644 index 0000000..a07fb79 --- /dev/null +++ b/environments/prod/modules/mysql/checksums.json @@ -0,0 +1,115 @@ +{ + "CHANGELOG.md": "b68d316974b7228a92b8a8c3b65bb9a4", + "CONTRIBUTING.md": "b78f71c1104f00538d50ad2775f58e95", + "Gemfile": "fda543906c8c75b06ea37c4d31b95292", + "LICENSE": "3b83ef96387f14655fc854ddc3c6bd57", + "NOTICE": "d43aacde256c8e55a50e2425e25b3a8a", + "README.md": "b4969d0b8649dd0f9c105ff22b66a871", + "Rakefile": "6db744f1deed7ae0746abe59e3d50863", + "TODO": "88ca4024a37992b46c34cb46e4ac39e6", + "examples/backup.pp": "a61c6f34f153a323209faf25948737f5", + "examples/bindings.pp": "35a8387f5c55fa2e479c513a67918674", + "examples/java.pp": "0ad9de4f9f2c049642bcf08124757085", + "examples/mysql_database.pp": "107ee8793f7b4a12cfca32eddccc6bbd", + "examples/mysql_db.pp": "55d2d603f9fb8ab3c8a781d08119aa69", + "examples/mysql_grant.pp": "cd42336a6c7b2d27f5d5d6d0e310ee1a", + "examples/mysql_plugin.pp": "4f86c988a99b131e736501a309604e94", + "examples/mysql_user.pp": "0b76964aebb01714745548e0f2f32fd3", + "examples/perl.pp": "454f14dc4492fcf04afbe81b2776917e", + "examples/python.pp": "355a7e1ea3978a8fd290b5bc28b63808", + "examples/ruby.pp": "a6ae0381aacc5a8d2c403e606c6df0f0", + "examples/server/account_security.pp": "375442b7886c01b42fbf75a1fcb31822", + "examples/server/config.pp": "659b7c40e9b55634721b3c33a8c6da98", + "examples/server.pp": "72e22552a95b9a5e4a349dbfc13639dc", + "lib/facter/mysql_server_id.rb": "10de205ca9dc83b68cf63b52d97346ec", + "lib/facter/mysql_version.rb": "e30648f3d394a1cbdd4e54eb5c28d28c", + "lib/facter/mysqld_version.rb": "720f447c97dd4ce099006cd5391ade57", + "lib/puppet/parser/functions/mysql_deepmerge.rb": "2b5040ee8cd75a81cf881851e922833a", + "lib/puppet/parser/functions/mysql_dirname.rb": "820ba09a6a0df639da560b41cb31242f", + "lib/puppet/parser/functions/mysql_password.rb": "365a0ba55f0d04d0f5d56abcd577ec7d", + "lib/puppet/parser/functions/mysql_strip_hash.rb": "3efe69f1eb189b2913e178b8472aaede", + "lib/puppet/provider/mysql.rb": "2696893220da9d341d5826c9e9d4ca19", + "lib/puppet/provider/mysql_database/mysql.rb": "5cf9b8044df4ec72a726b5f781c84f8f", + "lib/puppet/provider/mysql_datadir/mysql.rb": "7c256b40cf96bdafde9c762ab867de80", + "lib/puppet/provider/mysql_grant/mysql.rb": "3630023349a4c8f06932938a26aaa2e6", + "lib/puppet/provider/mysql_plugin/mysql.rb": "49128d2a9a547c4945735bfd75fd6d6c", + "lib/puppet/provider/mysql_user/mysql.rb": "dc09eef4e1d879ffe4ee8eb9a7b3026c", + "lib/puppet/type/mysql_database.rb": "438eafbfecc30605462a220d92cf9603", + "lib/puppet/type/mysql_datadir.rb": "f19e548062e81f424646c193b086aaf6", + "lib/puppet/type/mysql_grant.rb": "afcc65b0394a23e95b8bf932bc6110b4", + "lib/puppet/type/mysql_plugin.rb": "0a52cead6c9283c9aa14ea71bdfa80bd", + "lib/puppet/type/mysql_user.rb": "8cc3c0851c06d01b310a7f551945e696", + "manifests/backup/mysqlbackup.pp": "66441b167e682689df21a4b37af175e8", + "manifests/backup/mysqldump.pp": "d858a7dda445c45952f6a7f029d41189", + "manifests/backup/xtrabackup.pp": "ebf4f4765fbbedbdc49acad9dd539a6b", + "manifests/bindings/client_dev.pp": "80753f1bf48de71d2ca1a6cfda4830be", + "manifests/bindings/daemon_dev.pp": "94d2d74afc2b247593877cebd548ed76", + "manifests/bindings/java.pp": "556b743dc162d2f3264708b0b7ba0328", + "manifests/bindings/perl.pp": "4ecbd448ceac580a07df34b5d3e24837", + "manifests/bindings/php.pp": "641139f1f27085b8e72ac73fb8bc39d8", + "manifests/bindings/python.pp": "ef9e674237eddd7e98ab307131b11069", + "manifests/bindings/ruby.pp": "32b0a32161f7a5b50562cb8e154207d9", + "manifests/bindings.pp": "8becf04105d44910f12986a58cd566f7", + "manifests/client/install.pp": "7b5810404cc9411ec38de404749f1266", + "manifests/client.pp": "c350bd6d108acb03a87b860b14d225ef", + "manifests/db.pp": "fada45e451ec7563dab714d12b9e330b", + "manifests/params.pp": "816c5cf78525ab2917da4ce1a888dfe6", + "manifests/server/account_security.pp": "36a293eec189ec1bdfe92b370d087085", + "manifests/server/backup.pp": "6ed4f8c9aac5d7f17bf5c10fceddde41", + "manifests/server/binarylog.pp": "d61af28d0f9c07cfa23c63a7cdd364bf", + "manifests/server/config.pp": "650f0ba40b9148e66f910465e63b1170", + "manifests/server/install.pp": "3cbbf313b940b582e73c229f4a8c0720", + "manifests/server/installdb.pp": "ccc13d3cbeb78b0c144006a256e40079", + "manifests/server/monitor.pp": "3810b5d756a3661e92d709115982cd59", + "manifests/server/mysqltuner.pp": "24d04e0b24cf3b31b6798bc76eed32b6", + "manifests/server/providers.pp": "87a019dce5bbb6b18c9aa61b5f99134c", + "manifests/server/root_password.pp": "5790e04defe2a64d145975abdfcfd3ac", + "manifests/server/service.pp": "e9d4256c2830a8b300822616c408ce95", + "manifests/server.pp": "3f3915a73ff075eca009f5656b225e6d", + "metadata.json": "2cc79c0e6902c12c1b33eca2e192ea29", + "spec/acceptance/mysql_backup_spec.rb": "ef6f0d033e87a8a9f8457f89ad8ea118", + "spec/acceptance/mysql_db_spec.rb": "c093f0d25c7000713b444706b6cfd1c7", + "spec/acceptance/mysql_helper.rb": "b95d8fd15325f1ece1cd652dc60c7608", + "spec/acceptance/mysql_server_spec.rb": "b3362859a6cdc38cfabf1c5a63a4fc57", + "spec/acceptance/nodesets/centos-7-x64.yml": "a713f3abd3657f0ae2878829badd23cd", + "spec/acceptance/nodesets/debian-8-x64.yml": "d2d2977900989f30086ad251a14a1f39", + "spec/acceptance/nodesets/default.yml": "b42da5a1ea0c964567ba7495574b8808", + "spec/acceptance/nodesets/docker/centos-7.yml": "8a3892807bdd62306ae4774f41ba11ae", + "spec/acceptance/nodesets/docker/debian-8.yml": "ac8e871d1068c96de5e85a89daaec6df", + "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "dc42ee922a96908d85b8f0f08203ce58", + "spec/acceptance/types/mysql_database_spec.rb": "047db055103fa9782bc6714297eb2a09", + "spec/acceptance/types/mysql_grant_spec.rb": "216d93f0937dbdb5fc58a61bd2150d62", + "spec/acceptance/types/mysql_plugin_spec.rb": "107f32d267daa847996ff992ccec8c06", + "spec/acceptance/types/mysql_user_spec.rb": "5d04dc3b3ffd0778f83038833ef72bf2", + "spec/classes/graceful_failures_spec.rb": "91481f693c3f71dff22524189438bcc6", + "spec/classes/mycnf_template_spec.rb": "07fc36d51b592eba1f94d39208c2701f", + "spec/classes/mysql_bindings_spec.rb": "14c5abbe4ae20278d1a4a02254bf312b", + "spec/classes/mysql_client_spec.rb": "b1fa7a5a06585697f7100d1891d359ed", + "spec/classes/mysql_server_account_security_spec.rb": "97b6caf04a7a75a88e58728edcfc11cb", + "spec/classes/mysql_server_backup_spec.rb": "964357b46daa237c92cc61b88a20093b", + "spec/classes/mysql_server_monitor_spec.rb": "aef3aa73265b42b66b963a09e5f32f1f", + "spec/classes/mysql_server_mysqltuner_spec.rb": "55ff6dad45a8dfef97d52dff0b111a36", + "spec/classes/mysql_server_spec.rb": "99806ad51b124bf8ca9dad201962b743", + "spec/defines/mysql_db_spec.rb": "afb2d6cfa051dd4d8301d7aede1aa1c4", + "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c", + "spec/spec_helper.rb": "b2db3bc02b4ac2fd5142a6621c641b07", + "spec/spec_helper_acceptance.rb": "2d7d8e8a6d1225ba6bf57a4f05dfff0c", + "spec/spec_helper_local.rb": "05db024c91eb5d5141b67d97b01a58b4", + "spec/unit/facter/mysql_server_id_spec.rb": "4a01f2323a400c82d89f37847c3c9611", + "spec/unit/facter/mysql_version_spec.rb": "f593c725b83a7bbf1f0fdc13bf1531df", + "spec/unit/facter/mysqld_version_spec.rb": "f5495c63a64d21be175d329cc5a3a8cd", + "spec/unit/puppet/functions/mysql_deepmerge_spec.rb": "f6102e1f82fb9f4aa38cbf3955ee5973", + "spec/unit/puppet/functions/mysql_password_spec.rb": "8fe68dc4ec13fc6ade18defe54cac8d4", + "spec/unit/puppet/provider/mysql_database/mysql_spec.rb": "98a799433ac10b237ac3ad27441610d4", + "spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb": "914bac73bb9841ec7d7041488a55d442", + "spec/unit/puppet/provider/mysql_user/mysql_spec.rb": "174e7e3a97529fdcc2e6ba24e65cf8d2", + "spec/unit/puppet/type/mysql_database_spec.rb": "9fa0d390c2e15c8a52fc2b981e2a63fc", + "spec/unit/puppet/type/mysql_grant_spec.rb": "1a003358dc88a74ef21fb33fc71debef", + "spec/unit/puppet/type/mysql_plugin_spec.rb": "7ebacf228dfd0c60811bdf8a28a329ff", + "spec/unit/puppet/type/mysql_user_spec.rb": "e902ad1b9578dce35e13750c7967ef1d", + "templates/meb.cnf.erb": "b6422b19ee97b8a2883bfac44fdc0292", + "templates/my.cnf.erb": "535d2ff37fea6b11ad928224965143d3", + "templates/my.cnf.pass.erb": "11f80afb0993a436f074a43f70733999", + "templates/mysqlbackup.sh.erb": "0cc74138c7a5dd5d3864f94227717bd4", + "templates/xtrabackup.sh.erb": "219b487c4faad80a897cda6973b81741" +} \ No newline at end of file diff --git a/environments/prod/modules/mysql/examples/backup.pp b/environments/prod/modules/mysql/examples/backup.pp new file mode 100644 index 0000000..4bdf804 --- /dev/null +++ b/environments/prod/modules/mysql/examples/backup.pp @@ -0,0 +1,9 @@ +class { 'mysql::server': + root_password => 'password' +} + +class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', +} diff --git a/environments/prod/modules/mysql/examples/bindings.pp b/environments/prod/modules/mysql/examples/bindings.pp new file mode 100644 index 0000000..e4e325c --- /dev/null +++ b/environments/prod/modules/mysql/examples/bindings.pp @@ -0,0 +1,3 @@ +class { 'mysql::bindings': + php_enable => true, +} diff --git a/environments/prod/modules/mysql/examples/java.pp b/environments/prod/modules/mysql/examples/java.pp new file mode 100644 index 0000000..0fc009a --- /dev/null +++ b/environments/prod/modules/mysql/examples/java.pp @@ -0,0 +1 @@ +class { 'mysql::java':} diff --git a/environments/prod/modules/mysql/examples/mysql_database.pp b/environments/prod/modules/mysql/examples/mysql_database.pp new file mode 100644 index 0000000..47a328c --- /dev/null +++ b/environments/prod/modules/mysql/examples/mysql_database.pp @@ -0,0 +1,17 @@ +class { 'mysql::server': + root_password => 'password' +} +mysql::db{ ['test1', 'test2', 'test3']: + ensure => present, + charset => 'utf8', + require => Class['mysql::server'], +} +mysql::db{ 'test4': + ensure => present, + charset => 'latin1', +} +mysql::db{ 'test5': + ensure => present, + charset => 'binary', + collate => 'binary', +} diff --git a/environments/prod/modules/mysql/examples/mysql_db.pp b/environments/prod/modules/mysql/examples/mysql_db.pp new file mode 100644 index 0000000..43c619f --- /dev/null +++ b/environments/prod/modules/mysql/examples/mysql_db.pp @@ -0,0 +1,17 @@ +class { 'mysql::server': + root_password => 'password' +} +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +mysql::db { "mydb_${fqdn}": + user => 'myuser', + password => 'mypass', + dbname => 'mydb', + host => $::fqdn, + grant => ['SELECT', 'UPDATE'], + tag => $domain, +} diff --git a/environments/prod/modules/mysql/examples/mysql_grant.pp b/environments/prod/modules/mysql/examples/mysql_grant.pp new file mode 100644 index 0000000..20fe78d --- /dev/null +++ b/environments/prod/modules/mysql/examples/mysql_grant.pp @@ -0,0 +1,5 @@ +mysql_grant{'test1@localhost/redmine.*': + user => 'test1@localhost', + table => 'redmine.*', + privileges => ['UPDATE'], +} diff --git a/environments/prod/modules/mysql/examples/mysql_plugin.pp b/environments/prod/modules/mysql/examples/mysql_plugin.pp new file mode 100644 index 0000000..d802759 --- /dev/null +++ b/environments/prod/modules/mysql/examples/mysql_plugin.pp @@ -0,0 +1,23 @@ +class { 'mysql::server': + root_password => 'password' +} + +$validate_password_soname = $::osfamily ? { + windows => 'validate_password.dll', + default => 'validate_password.so' +} + +mysql::plugin { 'validate_password': + ensure => present, + soname => $validate_password_soname, +} + +$auth_socket_soname = $::osfamily ? { + windows => 'auth_socket.dll', + default => 'auth_socket.so' +} + +mysql::plugin { 'auth_socket': + ensure => present, + soname => $auth_socket_soname, +} diff --git a/environments/prod/modules/mysql/examples/mysql_user.pp b/environments/prod/modules/mysql/examples/mysql_user.pp new file mode 100644 index 0000000..c471868 --- /dev/null +++ b/environments/prod/modules/mysql/examples/mysql_user.pp @@ -0,0 +1,32 @@ +$mysql_root_pw = 'password' + +class { 'mysql::server': + root_password => 'password', +} + +mysql_user{ 'redmine@localhost': + ensure => present, + password_hash => mysql_password('redmine'), + require => Class['mysql::server'], +} + +mysql_user{ 'dan@localhost': + ensure => present, + password_hash => mysql_password('blah') +} + +mysql_user{ 'dan@%': + ensure => present, + password_hash => mysql_password('blah'), +} + +mysql_user{ 'socketplugin@%': + ensure => present, + plugin => 'unix_socket', +} + +mysql_user{ 'socketplugin@%': + ensure => present, + password_hash => mysql_password('blah'), + plugin => 'mysql_native_password', +} diff --git a/environments/prod/modules/mysql/examples/perl.pp b/environments/prod/modules/mysql/examples/perl.pp new file mode 100644 index 0000000..1bbd3ab --- /dev/null +++ b/environments/prod/modules/mysql/examples/perl.pp @@ -0,0 +1 @@ +include mysql::bindings::perl diff --git a/environments/prod/modules/mysql/examples/python.pp b/environments/prod/modules/mysql/examples/python.pp new file mode 100644 index 0000000..49ddddb --- /dev/null +++ b/environments/prod/modules/mysql/examples/python.pp @@ -0,0 +1 @@ +class { 'mysql::bindings::python':} diff --git a/environments/prod/modules/mysql/examples/ruby.pp b/environments/prod/modules/mysql/examples/ruby.pp new file mode 100644 index 0000000..6717256 --- /dev/null +++ b/environments/prod/modules/mysql/examples/ruby.pp @@ -0,0 +1 @@ +include mysql::bindings::ruby diff --git a/environments/prod/modules/mysql/examples/server.pp b/environments/prod/modules/mysql/examples/server.pp new file mode 100644 index 0000000..8afdd00 --- /dev/null +++ b/environments/prod/modules/mysql/examples/server.pp @@ -0,0 +1,3 @@ +class { 'mysql::server': + root_password => 'password', +} diff --git a/environments/prod/modules/mysql/examples/server/account_security.pp b/environments/prod/modules/mysql/examples/server/account_security.pp new file mode 100644 index 0000000..cb59f4f --- /dev/null +++ b/environments/prod/modules/mysql/examples/server/account_security.pp @@ -0,0 +1,4 @@ +class { 'mysql::server': + root_password => 'password', +} +class { 'mysql::server::account_security': } diff --git a/environments/prod/modules/mysql/examples/server/config.pp b/environments/prod/modules/mysql/examples/server/config.pp new file mode 100644 index 0000000..2cde11b --- /dev/null +++ b/environments/prod/modules/mysql/examples/server/config.pp @@ -0,0 +1,3 @@ +mysql::server::config { 'testfile': + +} diff --git a/environments/prod/modules/mysql/lib/facter/mysql_server_id.rb b/environments/prod/modules/mysql/lib/facter/mysql_server_id.rb new file mode 100644 index 0000000..3cb39e5 --- /dev/null +++ b/environments/prod/modules/mysql/lib/facter/mysql_server_id.rb @@ -0,0 +1,9 @@ +def get_mysql_id + Facter.value(:macaddress).split(':')[2..-1].inject(0) { |total,value| (total << 6) + value.hex } +end + +Facter.add("mysql_server_id") do + setcode do + get_mysql_id rescue nil + end +end diff --git a/environments/prod/modules/mysql/lib/facter/mysql_version.rb b/environments/prod/modules/mysql/lib/facter/mysql_version.rb new file mode 100644 index 0000000..62fba61 --- /dev/null +++ b/environments/prod/modules/mysql/lib/facter/mysql_version.rb @@ -0,0 +1,8 @@ +Facter.add("mysql_version") do + setcode do + mysql_ver = Facter::Util::Resolution.exec('mysql --version') + if mysql_ver + mysql_ver.match(/\d+\.\d+\.\d+/)[0] + end + end +end diff --git a/environments/prod/modules/mysql/lib/facter/mysqld_version.rb b/environments/prod/modules/mysql/lib/facter/mysqld_version.rb new file mode 100644 index 0000000..c3711cc --- /dev/null +++ b/environments/prod/modules/mysql/lib/facter/mysqld_version.rb @@ -0,0 +1,5 @@ +Facter.add("mysqld_version") do + setcode do + Facter::Util::Resolution.exec('mysqld -V 2>/dev/null') + end +end diff --git a/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb new file mode 100644 index 0000000..aca9c7a --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb @@ -0,0 +1,58 @@ +module Puppet::Parser::Functions + newfunction(:mysql_deepmerge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Recursively merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = mysql_deepmerge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + + When there is a duplicate key that is a hash, they are recursively merged. + When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), + the rightmost style will win. + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("mysql_deepmerge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + result = Hash.new + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + # If the argument was not a hash, skip it. + unless arg.is_a?(Hash) + raise Puppet::ParseError, "mysql_deepmerge: unexpected argument type #{arg.class}, only expects hash arguments" + end + + # Now we have to traverse our hash assigning our non-hash values + # to the matching keys in our result while following our hash values + # and repeating the process. + overlay( result, arg ) + end + return( result ) + end +end + +def has_normalized!(hash, key) + return true if hash.has_key?( key ) + return false unless key.match(/-|_/) + other_key = key.include?('-') ? key.gsub( '-', '_' ) : key.gsub( '_', '-' ) + return false unless hash.has_key?( other_key ) + hash[key] = hash.delete( other_key ) + return true; +end + +def overlay( hash1, hash2 ) + hash2.each do |key, value| + if(has_normalized!( hash1, key ) and value.is_a?(Hash) and hash1[key].is_a?(Hash)) + overlay( hash1[key], value ) + else + hash1[key] = value + end + end +end diff --git a/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb new file mode 100644 index 0000000..5d0ef55 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb @@ -0,0 +1,15 @@ +module Puppet::Parser::Functions + newfunction(:mysql_dirname, :type => :rvalue, :doc => <<-EOS + Returns the dirname of a path. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "mysql_dirname(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size < 1 + + path = arguments[0] + return File.dirname(path) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_password.rb b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_password.rb new file mode 100644 index 0000000..74d7fa8 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_password.rb @@ -0,0 +1,17 @@ +# hash a string as mysql's "PASSWORD()" function would do it +require 'digest/sha1' + +module Puppet::Parser::Functions + newfunction(:mysql_password, :type => :rvalue, :doc => <<-EOS + Returns the mysql password hash from the clear text password. + EOS + ) do |args| + + raise(Puppet::ParseError, 'mysql_password(): Wrong number of arguments ' + + "given (#{args.size} for 1)") if args.size != 1 + + return '' if args[0].empty? + return args[0] if args[0] =~ /\*[A-F0-9]{40}$/ + '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(args[0])).upcase + end +end diff --git a/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb new file mode 100644 index 0000000..8e850d0 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:mysql_strip_hash, :type => :rvalue, :arity => 1, :doc => <<-EOS +TEMPORARY FUNCTION: EXPIRES 2014-03-10 +When given a hash this function strips out all blank entries. +EOS + ) do |args| + + hash = args[0] + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'mysql_strip_hash(): Requires hash to work with') + end + + # Filter out all the top level blanks. + hash.reject{|k,v| v == ''}.each do |k,v| + if v.is_a?(Hash) + v.reject!{|ki,vi| vi == '' } + end + end + + end +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql.rb new file mode 100644 index 0000000..181d788 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql.rb @@ -0,0 +1,115 @@ +class Puppet::Provider::Mysql < Puppet::Provider + + # Without initvars commands won't work. + initvars + + # Make sure we find mysql commands on CentOS and FreeBSD + ENV['PATH']=ENV['PATH'] + ':/usr/libexec:/usr/local/libexec:/usr/local/bin' + + commands :mysql => 'mysql' + commands :mysqld => 'mysqld' + commands :mysqladmin => 'mysqladmin' + + # Optional defaults file + def self.defaults_file + if File.file?("#{Facter.value(:root_home)}/.my.cnf") + "--defaults-extra-file=#{Facter.value(:root_home)}/.my.cnf" + else + nil + end + end + + def self.mysqld_type + # find the mysql "dialect" like mariadb / mysql etc. + mysqld_version_string.scan(/mariadb/i) { return "mariadb" } + mysqld_version_string.scan(/\s\(percona/i) { return "percona" } + return "mysql" + end + + def mysqld_type + self.class.mysqld_type + end + + def self.mysqld_version_string + # As the possibility of the mysqld being remote we need to allow the version string to be overridden, this can be done by facter.value as seen below. In the case that it has not been set and the facter value is nil we use the mysql -v command to ensure we report the correct version of mysql for later use cases. + @mysqld_version_string ||= Facter.value(:mysqld_version) || mysqld('-V') + end + + def mysqld_version_string + self.class.mysqld_version_string + end + + def self.mysqld_version + # note: be prepared for '5.7.6-rc-log' etc results + # versioncmp detects 5.7.6-log to be newer then 5.7.6 + # this is why we need the trimming. + mysqld_version_string.scan(/\d+\.\d+\.\d+/).first unless mysqld_version_string.nil? + end + + def mysqld_version + self.class.mysqld_version + end + + def defaults_file + self.class.defaults_file + end + + def self.users + mysql([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"].compact).split("\n") + end + + # Optional parameter to run a statement on the MySQL system database. + def self.system_database + '--database=mysql' + end + + def system_database + self.class.system_database + end + + # Take root@localhost and munge it to 'root'@'localhost' + def self.cmd_user(user) + "'#{user.sub('@', "'@'")}'" + end + + # Take root.* and return ON `root`.* + def self.cmd_table(table) + table_string = '' + + # We can't escape *.* so special case this. + if table == '*.*' + table_string << '*.*' + # Special case also for PROCEDURES + elsif table.start_with?('PROCEDURE ') + table_string << table.sub(/^PROCEDURE (.*)(\..*)/, 'PROCEDURE `\1`\2') + else + table_string << table.sub(/^(.*)(\..*)/, '`\1`\2') + end + table_string + end + + def self.cmd_privs(privileges) + if privileges.include?('ALL') + return 'ALL PRIVILEGES' + else + priv_string = '' + privileges.each do |priv| + priv_string << "#{priv}, " + end + end + # Remove trailing , from the last element. + priv_string.sub(/, $/, '') + end + + # Take in potential options and build up a query string with them. + def self.cmd_options(options) + option_string = '' + options.each do |opt| + if opt == 'GRANT' + option_string << ' WITH GRANT OPTION' + end + end + option_string + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb new file mode 100644 index 0000000..4587c18 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb @@ -0,0 +1,68 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_database).provide(:mysql, :parent => Puppet::Provider::Mysql) do + desc 'Manages MySQL databases.' + + commands :mysql => 'mysql' + + def self.instances + mysql([defaults_file, '-NBe', 'show databases'].compact).split("\n").collect do |name| + attributes = {} + mysql([defaults_file, '-NBe', "show variables like '%_database'", name].compact).split("\n").each do |line| + k,v = line.split(/\s/) + attributes[k] = v + end + new(:name => name, + :ensure => :present, + :charset => attributes['character_set_database'], + :collate => attributes['collation_database'] + ) + end + end + + # We iterate over each mysql_database entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + databases = instances + resources.keys.each do |database| + if provider = databases.find { |db| db.name == database } + resources[database].provider = provider + end + end + end + + def create + mysql([defaults_file, '-NBe', "create database if not exists `#{@resource[:name]}` character set `#{@resource[:charset]}` collate `#{@resource[:collate]}`"].compact) + + @property_hash[:ensure] = :present + @property_hash[:charset] = @resource[:charset] + @property_hash[:collate] = @resource[:collate] + + exists? ? (return true) : (return false) + end + + def destroy + mysql([defaults_file, '-NBe', "drop database if exists `#{@resource[:name]}`"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + mk_resource_methods + + def charset=(value) + mysql([defaults_file, '-NBe', "alter database `#{resource[:name]}` CHARACTER SET #{value}"].compact) + @property_hash[:charset] = value + charset == value ? (return true) : (return false) + end + + def collate=(value) + mysql([defaults_file, '-NBe', "alter database `#{resource[:name]}` COLLATE #{value}"].compact) + @property_hash[:collate] = value + collate == value ? (return true) : (return false) + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb new file mode 100644 index 0000000..620231e --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb @@ -0,0 +1,78 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_datadir).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'manage data directories for mysql instances' + + initvars + + # Make sure we find mysqld on CentOS and mysql_install_db on Gentoo + ENV['PATH']=ENV['PATH'] + ':/usr/libexec:/usr/share/mysql/scripts:/opt/rh/mysql55/root/usr/bin:/opt/rh/mysql55/root/usr/libexec' + + commands :mysqld => 'mysqld' + commands :mysql_install_db => 'mysql_install_db' + + def create + name = @resource[:name] + insecure = @resource.value(:insecure) || true + defaults_extra_file = @resource.value(:defaults_extra_file) + user = @resource.value(:user) || "mysql" + basedir = @resource.value(:basedir) + datadir = @resource.value(:datadir) || @resource[:name] + log_error = @resource.value(:log_error) || "/var/tmp/mysqld_initialize.log" + + unless defaults_extra_file.nil? + if File.exist?(defaults_extra_file) + defaults_extra_file="--defaults-extra-file=#{defaults_extra_file}" + else + raise ArgumentError, "Defaults-extra-file #{defaults_extra_file} is missing" + end + end + + if insecure == true + initialize="--initialize-insecure" + else + initialize="--initialize" + end + + opts = [ defaults_extra_file ] + %w(basedir datadir user).each do |opt| + val = eval(opt) + opts<<"--#{opt}=#{val}" unless val.nil? + end + + if mysqld_version.nil? + debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(" ")}") + mysql_install_db(opts.compact) + else + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + opts<<"--log-error=#{log_error}" + opts<<"#{initialize}" + debug("Initializing MySQL data directory >= 5.7.6 with mysqld: #{opts.compact.join(" ")}") + mysqld(opts.compact) + else + debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(" ")}") + mysql_install_db(opts.compact) + end + end + + exists? + end + + def destroy + name = @resource[:name] + raise ArgumentError, "ERROR: Resource can not be removed" + end + + def exists? + datadir = @resource[:datadir] + (File.directory?("#{datadir}/mysql")) && (Dir.entries("#{datadir}/mysql") - %w{ . .. }).any? + end + + ## + ## MySQL datadir properties + ## + + # Generates method for all properties of the property_hash + mk_resource_methods + +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb new file mode 100644 index 0000000..40623af --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb @@ -0,0 +1,176 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_grant).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'Set grants for users in MySQL.' + + def self.instances + instances = [] + users.select{ |user| user =~ /.+@/ }.collect do |user| + user_string = self.cmd_user(user) + query = "SHOW GRANTS FOR #{user_string};" + begin + grants = mysql([defaults_file, "-NBe", query].compact) + rescue Puppet::ExecutionFailure => e + # Silently ignore users with no grants. Can happen e.g. if user is + # defined with fqdn and server is run with skip-name-resolve. Example: + # Default root user created by mysql_install_db on a host with fqdn + # of myhost.mydomain.my: root@myhost.mydomain.my, when MySQL is started + # with --skip-name-resolve. + if e.inspect =~ /There is no such grant defined for user/ + next + else + raise Puppet::Error, "#mysql had an error -> #{e.inspect}" + end + end + # Once we have the list of grants generate entries for each. + grants.each_line do |grant| + # Match the munges we do in the type. + munged_grant = grant.delete("'").delete("`").delete('"') + # Matching: GRANT (SELECT, UPDATE) PRIVILEGES ON (*.*) TO ('root')@('127.0.0.1') (WITH GRANT OPTION) + if match = munged_grant.match(/^GRANT\s(.+)\sON\s(.+)\sTO\s(.*)@(.*?)(\s.*)?$/) + privileges, table, user, host, rest = match.captures + table.gsub!('\\\\', '\\') + + # split on ',' if it is not a non-'('-containing string followed by a + # closing parenthesis ')'-char - e.g. only split comma separated elements not in + # parentheses + stripped_privileges = privileges.strip.split(/\s*,\s*(?![^(]*\))/).map do |priv| + # split and sort the column_privileges in the parentheses and rejoin + if priv.include?('(') + type, col=priv.strip.split(/\s+|\b/,2) + type.upcase + " (" + col.slice(1...-1).strip.split(/\s*,\s*/).sort.join(', ') + ")" + else + # Once we split privileges up on the , we need to make sure we + # shortern ALL PRIVILEGES to just all. + priv == 'ALL PRIVILEGES' ? 'ALL' : priv.strip + end + end + # Same here, but to remove OPTION leaving just GRANT. + if rest.match(/WITH\sGRANT\sOPTION/) + options = ['GRANT'] + else + options = ['NONE'] + end + # fix double backslash that MySQL prints, so resources match + table.gsub!("\\\\", "\\") + # We need to return an array of instances so capture these + instances << new( + :name => "#{user}@#{host}/#{table}", + :ensure => :present, + :privileges => stripped_privileges.sort, + :table => table, + :user => "#{user}@#{host}", + :options => options + ) + end + end + end + return instances + end + + def self.prefetch(resources) + users = instances + resources.keys.each do |name| + if provider = users.find { |user| user.name == name } + resources[name].provider = provider + end + end + end + + def grant(user, table, privileges, options) + user_string = self.class.cmd_user(user) + priv_string = self.class.cmd_privs(privileges) + table_string = self.class.cmd_table(table) + query = "GRANT #{priv_string}" + query << " ON #{table_string}" + query << " TO #{user_string}" + query << self.class.cmd_options(options) unless options.nil? + mysql([defaults_file, system_database, '-e', query].compact) + end + + def create + grant(@resource[:user], @resource[:table], @resource[:privileges], @resource[:options]) + + @property_hash[:ensure] = :present + @property_hash[:table] = @resource[:table] + @property_hash[:user] = @resource[:user] + @property_hash[:options] = @resource[:options] if @resource[:options] + @property_hash[:privileges] = @resource[:privileges] + + exists? ? (return true) : (return false) + end + + def revoke(user, table, revoke_privileges = ['ALL']) + user_string = self.class.cmd_user(user) + table_string = self.class.cmd_table(table) + priv_string = self.class.cmd_privs(revoke_privileges) + # revoke grant option needs to be a extra query, because + # "REVOKE ALL PRIVILEGES, GRANT OPTION [..]" is only valid mysql syntax + # if no ON clause is used. + # It hast to be executed before "REVOKE ALL [..]" since a GRANT has to + # exist to be executed successfully + if revoke_privileges.include? 'ALL' + query = "REVOKE GRANT OPTION ON #{table_string} FROM #{user_string}" + mysql([defaults_file, system_database, '-e', query].compact) + end + query = "REVOKE #{priv_string} ON #{table_string} FROM #{user_string}" + mysql([defaults_file, system_database, '-e', query].compact) + end + + def destroy + # if the user was dropped, it'll have been removed from the user hash + # as the grants are alraedy removed by the DROP statement + if self.class.users.include? @property_hash[:user] + revoke(@property_hash[:user], @property_hash[:table]) + end + @property_hash.clear + + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + def flush + @property_hash.clear + mysql([defaults_file, '-NBe', 'FLUSH PRIVILEGES'].compact) + end + + mk_resource_methods + + def diff_privileges(privileges_old, privileges_new) + diff = {:revoke => Array.new, :grant => Array.new} + if privileges_old.include? 'ALL' + diff[:revoke] = privileges_old + diff[:grant] = privileges_new + elsif privileges_new.include? 'ALL' + diff[:grant] = privileges_new + else + diff[:revoke] = privileges_old - privileges_new + diff[:grant] = privileges_new - privileges_old + end + return diff + end + + def privileges=(privileges) + diff = diff_privileges(@property_hash[:privileges], privileges) + if not diff[:revoke].empty? + revoke(@property_hash[:user], @property_hash[:table], diff[:revoke]) + end + if not diff[:grant].empty? + grant(@property_hash[:user], @property_hash[:table], diff[:grant], @property_hash[:options]) + end + @property_hash[:privileges] = privileges + self.privileges + end + + def options=(options) + revoke(@property_hash[:user], @property_hash[:table]) + grant(@property_hash[:user], @property_hash[:table], @property_hash[:privileges], options) + @property_hash[:options] = options + + self.options + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb new file mode 100644 index 0000000..2cb640b --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb @@ -0,0 +1,53 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_plugin).provide(:mysql, :parent => Puppet::Provider::Mysql) do + desc 'Manages MySQL plugins.' + + commands :mysql => 'mysql' + + def self.instances + mysql([defaults_file, '-NBe', 'show plugins'].compact).split("\n").collect do |line| + name, status, type, library, license = line.split(/\t/) + new(:name => name, + :ensure => :present, + :soname => library + ) + end + end + + # We iterate over each mysql_plugin entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + plugins = instances + resources.keys.each do |plugin| + if provider = plugins.find { |pl| pl.name == plugin } + resources[plugin].provider = provider + end + end + end + + def create + # Use plugin_name.so as soname if it's not specified. This won't work on windows as + # there it should be plugin_name.dll + @resource[:soname].nil? ? (soname=@resource[:name] + '.so') : (soname=@resource[:soname]) + mysql([defaults_file, '-NBe', "install plugin #{@resource[:name]} soname '#{soname}'"].compact) + + @property_hash[:ensure] = :present + @property_hash[:soname] = @resource[:soname] + + exists? ? (return true) : (return false) + end + + def destroy + mysql([defaults_file, '-NBe', "uninstall plugin #{@resource[:name]}"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + mk_resource_methods + +end diff --git a/environments/prod/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb b/environments/prod/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb new file mode 100644 index 0000000..e5200a1 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb @@ -0,0 +1,197 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_user).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'manage users for a mysql database.' + commands :mysql => 'mysql' + + # Build a property_hash containing all the discovered information about MySQL + # users. + def self.instances + users = mysql([defaults_file, '-NBe', + "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"].compact).split("\n") + # To reduce the number of calls to MySQL we collect all the properties in + # one big swoop. + users.collect do |name| + if mysqld_version.nil? + ## Default ... + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + else + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + else + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + end + end + @max_user_connections, @max_connections_per_hour, @max_queries_per_hour, + @max_updates_per_hour, ssl_type, ssl_cipher, x509_issuer, x509_subject, + @password, @plugin = mysql([defaults_file, "-NBe", query].compact).split(/\s/) + @tls_options = parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) + + new(:name => name, + :ensure => :present, + :password_hash => @password, + :plugin => @plugin, + :max_user_connections => @max_user_connections, + :max_connections_per_hour => @max_connections_per_hour, + :max_queries_per_hour => @max_queries_per_hour, + :max_updates_per_hour => @max_updates_per_hour, + :tls_options => @tls_options, + ) + end + end + + # We iterate over each mysql_user entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + users = instances + resources.keys.each do |name| + if provider = users.find { |user| user.name == name } + resources[name].provider = provider + end + end + end + + def create + merged_name = @resource[:name].sub('@', "'@'") + password_hash = @resource.value(:password_hash) + plugin = @resource.value(:plugin) + max_user_connections = @resource.value(:max_user_connections) || 0 + max_connections_per_hour = @resource.value(:max_connections_per_hour) || 0 + max_queries_per_hour = @resource.value(:max_queries_per_hour) || 0 + max_updates_per_hour = @resource.value(:max_updates_per_hour) || 0 + tls_options = @resource.value(:tls_options) || ['NONE'] + + # Use CREATE USER to be compatible with NO_AUTO_CREATE_USER sql_mode + # This is also required if you want to specify a authentication plugin + if !plugin.nil? + if plugin == 'sha256_password' and !password_hash.nil? + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}' AS '#{password_hash}'"].compact) + else + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}'"].compact) + end + @property_hash[:ensure] = :present + @property_hash[:plugin] = plugin + else + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}'"].compact) + @property_hash[:ensure] = :present + @property_hash[:password_hash] = password_hash + end + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}"].compact) + @property_hash[:max_user_connections] = max_user_connections + @property_hash[:max_connections_per_hour] = max_connections_per_hour + @property_hash[:max_queries_per_hour] = max_queries_per_hour + @property_hash[:max_updates_per_hour] = max_updates_per_hour + + merged_tls_options = tls_options.join(' AND ') + if (((mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0) or + (mysqld_type == 'mariadb' and Puppet::Util::Package.versioncmp(mysqld_version, '10.2.0') >= 0)) + mysql([defaults_file, system_database, '-e', "ALTER USER '#{merged_name}' REQUIRE #{merged_tls_options}"].compact) + else + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO '#{merged_name}' REQUIRE #{merged_tls_options}"].compact) + end + @property_hash[:tls_options] = tls_options + + exists? ? (return true) : (return false) + end + + def destroy + merged_name = @resource[:name].sub('@', "'@'") + mysql([defaults_file, system_database, '-e', "DROP USER '#{merged_name}'"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + ## + ## MySQL user properties + ## + + # Generates method for all properties of the property_hash + mk_resource_methods + + def password_hash=(string) + merged_name = self.class.cmd_user(@resource[:name]) + + # We have a fact for the mysql version ... + if mysqld_version.nil? + # default ... if mysqld_version does not work + mysql([defaults_file, system_database, '-e', "SET PASSWORD FOR #{merged_name} = '#{string}'"].compact) + else + # Version >= 5.7.6 (many password related changes) + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + if string.match(/^\*/) + mysql([defaults_file, system_database, '-e', "ALTER USER #{merged_name} IDENTIFIED WITH mysql_native_password AS '#{string}'"].compact) + else + raise ArgumentError, "Only mysql_native_password (*ABCD...XXX) hashes are supported" + end + else + # older versions + mysql([defaults_file, system_database, '-e', "SET PASSWORD FOR #{merged_name} = '#{string}'"].compact) + end + end + + password_hash == string ? (return true) : (return false) + end + + def max_user_connections=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_USER_CONNECTIONS #{int}"].compact).chomp + + max_user_connections == int ? (return true) : (return false) + end + + def max_connections_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_CONNECTIONS_PER_HOUR #{int}"].compact).chomp + + max_connections_per_hour == int ? (return true) : (return false) + end + + def max_queries_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_QUERIES_PER_HOUR #{int}"].compact).chomp + + max_queries_per_hour == int ? (return true) : (return false) + end + + def max_updates_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_UPDATES_PER_HOUR #{int}"].compact).chomp + + max_updates_per_hour == int ? (return true) : (return false) + end + + def tls_options=(array) + merged_name = self.class.cmd_user(@resource[:name]) + merged_tls_options = array.join(' AND ') + if (((mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0) or + (mysqld_type == 'mariadb' and Puppet::Util::Package.versioncmp(mysqld_version, '10.2.0') >= 0)) + mysql([defaults_file, system_database, '-e', "ALTER USER #{merged_name} REQUIRE #{merged_tls_options}"].compact) + else + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} REQUIRE #{merged_tls_options}"].compact) + end + + tls_options == array ? (return true) : (return false) + end + + def self.parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) + if ssl_type == 'ANY' + return ['SSL'] + elsif ssl_type == 'X509' + return ['X509'] + elsif ssl_type == 'SPECIFIED' + options = [] + options << "CIPHER #{ssl_cipher}" if not ssl_cipher.nil? and not ssl_cipher.empty? + options << "ISSUER #{x509_issuer}" if not x509_issuer.nil? and not x509_issuer.empty? + options << "SUBJECT #{x509_subject}" if not x509_subject.nil? and not x509_subject.empty? + return options + else + return ['NONE'] + end + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/type/mysql_database.rb b/environments/prod/modules/mysql/lib/puppet/type/mysql_database.rb new file mode 100644 index 0000000..1f94d5f --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/type/mysql_database.rb @@ -0,0 +1,25 @@ +Puppet::Type.newtype(:mysql_database) do + @doc = 'Manage MySQL databases.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:class) { 'mysql::server' } + + newparam(:name, :namevar => true) do + desc 'The name of the MySQL database to manage.' + end + + newproperty(:charset) do + desc 'The CHARACTER SET setting for the database' + defaultto :utf8 + newvalue(/^\S+$/) + end + + newproperty(:collate) do + desc 'The COLLATE setting for the database' + defaultto :utf8_general_ci + newvalue(/^\S+$/) + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/type/mysql_datadir.rb b/environments/prod/modules/mysql/lib/puppet/type/mysql_datadir.rb new file mode 100644 index 0000000..367767d --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/type/mysql_datadir.rb @@ -0,0 +1,35 @@ +Puppet::Type.newtype(:mysql_datadir) do + @doc = 'Manage MySQL datadirs with mysql_install_db OR mysqld (5.7.6 and above).' + + ensurable + + autorequire(:package) { 'mysql-server' } + + newparam(:datadir, :namevar => true) do + desc "The datadir name" + end + + newparam(:basedir) do + desc 'The basedir name, default /usr.' + newvalues(/^\//) + end + + newparam(:user) do + desc 'The user for the directory default mysql (name, not uid).' + end + + newparam(:defaults_extra_file) do + desc "MySQL defaults-extra-file with absolute path (*.cnf)." + newvalues(/^\/.*\.cnf$/) + end + + newparam(:insecure, :boolean => true) do + desc "Insecure initialization (needed for 5.7.6++)." + end + + newparam(:log_error) do + desc "The path to the mysqld error log file (used with the --log-error option)" + newvalues(/^\//) + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/type/mysql_grant.rb b/environments/prod/modules/mysql/lib/puppet/type/mysql_grant.rb new file mode 100644 index 0000000..7385f93 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/type/mysql_grant.rb @@ -0,0 +1,103 @@ +# This has to be a separate type to enable collecting +Puppet::Type.newtype(:mysql_grant) do + @doc = "Manage a MySQL user's rights." + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:mysql_user) { self[:user] } + + def initialize(*args) + super + # Forcibly munge any privilege with 'ALL' in the array to exist of just + # 'ALL'. This can't be done in the munge in the property as that iterates + # over the array and there's no way to replace the entire array before it's + # returned to the provider. + if self[:ensure] == :present and Array(self[:privileges]).count > 1 and self[:privileges].to_s.include?('ALL') + self[:privileges] = 'ALL' + end + # Sort the privileges array in order to ensure the comparision in the provider + # self.instances method match. Otherwise this causes it to keep resetting the + # privileges. + self[:privileges] = Array(self[:privileges]).map{ |priv| + # split and sort the column_privileges in the parentheses and rejoin + if priv.include?('(') + type, col=priv.strip.split(/\s+|\b/,2) + type.upcase + " (" + col.slice(1...-1).strip.split(/\s*,\s*/).sort.join(', ') + ")" + else + priv.strip.upcase + end + }.uniq.reject{|k| k == 'GRANT' or k == 'GRANT OPTION'}.sort! + end + + validate do + fail('privileges parameter is required.') if self[:ensure] == :present and self[:privileges].nil? + fail('table parameter is required.') if self[:ensure] == :present and self[:table].nil? + fail('user parameter is required.') if self[:ensure] == :present and self[:user].nil? + fail('name must match user and table parameters') if self[:name] != "#{self[:user]}/#{self[:table]}" + end + + newparam(:name, :namevar => true) do + desc 'Name to describe the grant.' + + munge do |value| + value.delete("'") + end + end + + newproperty(:privileges, :array_matching => :all) do + desc 'Privileges for user' + end + + newproperty(:table) do + desc 'Table to apply privileges to.' + + munge do |value| + value.delete("`") + end + + newvalues(/.*\..*/,/@/) + end + + newproperty(:user) do + desc 'User to operate on.' + validate do |value| + # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html + # If at least one special char is used, string must be quoted + + # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 + if matches = /^(['`"])((?!\1).)*\1@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[2] + host_part = matches[3] + elsif matches = /^([0-9a-zA-Z$_]*)@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + elsif matches = /^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + else + raise(ArgumentError, "Invalid database user #{value}") + end + + mysql_version = Facter.value(:mysql_version) + unless mysql_version.nil? + if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 and user_part.size > 16 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 16 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 and user_part.size > 32 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 32 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 and user_part.size > 80 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 80 characters') + end + end + end + + munge do |value| + matches = /^((['`"]?).*\2)@(.+)$/.match(value) + "#{matches[1]}@#{matches[3].downcase}" + end + end + + newproperty(:options, :array_matching => :all) do + desc 'Options to grant.' + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/type/mysql_plugin.rb b/environments/prod/modules/mysql/lib/puppet/type/mysql_plugin.rb new file mode 100644 index 0000000..e827920 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/type/mysql_plugin.rb @@ -0,0 +1,17 @@ +Puppet::Type.newtype(:mysql_plugin) do + @doc = 'Manage MySQL plugins.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + + newparam(:name, :namevar => true) do + desc 'The name of the MySQL plugin to manage.' + end + + newproperty(:soname) do + desc 'The name of the library' + newvalue(/^\w+\.\w+$/) + end + +end diff --git a/environments/prod/modules/mysql/lib/puppet/type/mysql_user.rb b/environments/prod/modules/mysql/lib/puppet/type/mysql_user.rb new file mode 100644 index 0000000..831a531 --- /dev/null +++ b/environments/prod/modules/mysql/lib/puppet/type/mysql_user.rb @@ -0,0 +1,105 @@ +# This has to be a separate type to enable collecting +Puppet::Type.newtype(:mysql_user) do + @doc = 'Manage a MySQL user. This includes management of users password as well as privileges.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:class) { 'mysql::server' } + + newparam(:name, :namevar => true) do + desc "The name of the user. This uses the 'username@hostname' or username@hostname." + validate do |value| + # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html + # If at least one special char is used, string must be quoted + + # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 + if matches = /^(['`"])((?:(?!\1).)*)\1@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[2] + host_part = matches[3] + elsif matches = /^([0-9a-zA-Z$_]*)@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + elsif matches = /^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + else + raise(ArgumentError, "Invalid database user #{value}") + end + + mysql_version = Facter.value(:mysql_version) + unless mysql_version.nil? + if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 and user_part.size > 16 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 16 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 and user_part.size > 32 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 32 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 and user_part.size > 80 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 80 characters') + end + end + end + + munge do |value| + matches = /^((['`"]?).*\2)@(.+)$/.match(value) + "#{matches[1]}@#{matches[3].downcase}" + end + end + + newproperty(:password_hash) do + desc 'The password hash of the user. Use mysql_password() for creating such a hash.' + newvalue(/\w*/) + end + + newproperty(:plugin) do + desc 'The authentication plugin of the user.' + newvalue(/\w+/) + end + + newproperty(:max_user_connections) do + desc "Max concurrent connections for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_connections_per_hour) do + desc "Max connections per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_queries_per_hour) do + desc "Max queries per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_updates_per_hour) do + desc "Max updates per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:tls_options, :array_matching => :all) do + desc "Options to that set the TLS-related REQUIRE attributes for the user." + validate do |value| + value = [value] if not value.is_a?(Array) + if value.include? 'NONE' or value.include? 'SSL' or value.include? 'X509' + if value.length > 1 + raise(ArgumentError, "REQUIRE tls options NONE, SSL and X509 cannot be used with other options, you may only use one of them.") + end + else + value.each do |opt| + if not o = opt.match(/^(CIPHER|ISSUER|SUBJECT)/i) + raise(ArgumentError, "Invalid tls option #{o}") + end + end + end + end + def insync?(is) + # The current value may be nil and we don't + # want to call sort on it so make sure we have arrays + if is.is_a?(Array) and @should.is_a?(Array) + is.sort == @should.sort + else + is == @should + end + end + end + +end diff --git a/environments/prod/modules/mysql/manifests/backup/mysqlbackup.pp b/environments/prod/modules/mysql/manifests/backup/mysqlbackup.pp new file mode 100644 index 0000000..ae70c1b --- /dev/null +++ b/environments/prod/modules/mysql/manifests/backup/mysqlbackup.pp @@ -0,0 +1,106 @@ +# See README.me for usage. +class mysql::backup::mysqlbackup ( + $backupuser = '', + $backuppassword = '', + $maxallowedpacket = '1M', + $backupdir = '', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = true, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + mysql_user { "${backupuser}@localhost": + ensure => $ensure, + password_hash => mysql_password($backuppassword), + require => Class['mysql::server::root_password'], + } + + package { 'meb': + ensure => $ensure, + } + + # http://dev.mysql.com/doc/mysql-enterprise-backup/3.11/en/mysqlbackup.privileges.html + mysql_grant { "${backupuser}@localhost/*.*": + ensure => $ensure, + user => "${backupuser}@localhost", + table => '*.*', + privileges => [ 'RELOAD', 'SUPER', 'REPLICATION CLIENT' ], + require => Mysql_user["${backupuser}@localhost"], + } + + mysql_grant { "${backupuser}@localhost/mysql.backup_progress": + ensure => $ensure, + user => "${backupuser}@localhost", + table => 'mysql.backup_progress', + privileges => [ 'CREATE', 'INSERT', 'DROP', 'UPDATE' ], + require => Mysql_user["${backupuser}@localhost"], + } + + mysql_grant { "${backupuser}@localhost/mysql.backup_history": + ensure => $ensure, + user => "${backupuser}@localhost", + table => 'mysql.backup_history', + privileges => [ 'CREATE', 'INSERT', 'SELECT', 'DROP', 'UPDATE' ], + require => Mysql_user["${backupuser}@localhost"], + } + + cron { 'mysqlbackup-weekly': + ensure => $ensure, + command => 'mysqlbackup backup', + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '0', + require => Package['meb'], + } + + cron { 'mysqlbackup-daily': + ensure => $ensure, + command => 'mysqlbackup --incremental backup', + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '1-6', + require => Package['meb'], + } + + $default_options = { + 'mysqlbackup' => { + 'backup-dir' => $backupdir, + 'with-timestamp' => true, + 'incremental_base' => 'history:last_backup', + 'incremental_backup_dir' => $backupdir, + 'user' => $backupuser, + 'password' => $backuppassword, + } + } + $options = mysql_deepmerge($default_options, $mysql::server::override_options) + + file { 'mysqlbackup-config-file': + path => '/etc/mysql/conf.d/meb.cnf', + content => template('mysql/meb.cnf.erb'), + mode => '0600', + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + +} diff --git a/environments/prod/modules/mysql/manifests/backup/mysqldump.pp b/environments/prod/modules/mysql/manifests/backup/mysqldump.pp new file mode 100644 index 0000000..3ff3c9b --- /dev/null +++ b/environments/prod/modules/mysql/manifests/backup/mysqldump.pp @@ -0,0 +1,73 @@ +# See README.me for usage. +class mysql::backup::mysqldump ( + $backupuser = '', + $backuppassword = '', + $backupdir = '', + $maxallowedpacket = '1M', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = false, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + ensure_packages(['bzip2']) + + mysql_user { "${backupuser}@localhost": + ensure => $ensure, + password_hash => mysql_password($backuppassword), + require => Class['mysql::server::root_password'], + } + + if $include_triggers { + $privs = [ 'SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER' ] + } else { + $privs = [ 'SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS' ] + } + + mysql_grant { "${backupuser}@localhost/*.*": + ensure => $ensure, + user => "${backupuser}@localhost", + table => '*.*', + privileges => $privs, + require => Mysql_user["${backupuser}@localhost"], + } + + cron { 'mysql-backup': + ensure => $ensure, + command => '/usr/local/sbin/mysqlbackup.sh', + user => 'root', + hour => $time[0], + minute => $time[1], + require => [File['mysqlbackup.sh'], Package['bzip2']], + } + + file { 'mysqlbackup.sh': + ensure => $ensure, + path => '/usr/local/sbin/mysqlbackup.sh', + mode => '0700', + owner => 'root', + group => $mysql::params::root_group, + content => template('mysql/mysqlbackup.sh.erb'), + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + +} diff --git a/environments/prod/modules/mysql/manifests/backup/xtrabackup.pp b/environments/prod/modules/mysql/manifests/backup/xtrabackup.pp new file mode 100644 index 0000000..185b29f --- /dev/null +++ b/environments/prod/modules/mysql/manifests/backup/xtrabackup.pp @@ -0,0 +1,67 @@ +# See README.me for usage. +class mysql::backup::xtrabackup ( + $xtrabackup_package_name = $mysql::params::xtrabackup_package_name, + $backupuser = '', + $backuppassword = '', + $backupdir = '', + $maxallowedpacket = '1M', + $backupmethod = 'mysqldump', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = true, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + package{ $xtrabackup_package_name: + ensure => $ensure, + } + + cron { 'xtrabackup-weekly': + ensure => $ensure, + command => "/usr/local/sbin/xtrabackup.sh ${backupdir}", + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '0', + require => Package[$xtrabackup_package_name], + } + + cron { 'xtrabackup-daily': + ensure => $ensure, + command => "/usr/local/sbin/xtrabackup.sh --incremental ${backupdir}", + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '1-6', + require => Package[$xtrabackup_package_name], + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + + file { 'xtrabackup.sh': + ensure => $ensure, + path => '/usr/local/sbin/xtrabackup.sh', + mode => '0700', + owner => 'root', + group => $mysql::params::root_group, + content => template('mysql/xtrabackup.sh.erb'), + } +} diff --git a/environments/prod/modules/mysql/manifests/bindings.pp b/environments/prod/modules/mysql/manifests/bindings.pp new file mode 100644 index 0000000..fa7b870 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings.pp @@ -0,0 +1,57 @@ +# See README.md. +class mysql::bindings ( + $install_options = undef, + # Boolean to determine if we should include the classes. + $java_enable = false, + $perl_enable = false, + $php_enable = false, + $python_enable = false, + $ruby_enable = false, + $client_dev = false, + $daemon_dev = false, + # Settings for the various classes. + $java_package_ensure = $mysql::params::java_package_ensure, + $java_package_name = $mysql::params::java_package_name, + $java_package_provider = $mysql::params::java_package_provider, + $perl_package_ensure = $mysql::params::perl_package_ensure, + $perl_package_name = $mysql::params::perl_package_name, + $perl_package_provider = $mysql::params::perl_package_provider, + $php_package_ensure = $mysql::params::php_package_ensure, + $php_package_name = $mysql::params::php_package_name, + $php_package_provider = $mysql::params::php_package_provider, + $python_package_ensure = $mysql::params::python_package_ensure, + $python_package_name = $mysql::params::python_package_name, + $python_package_provider = $mysql::params::python_package_provider, + $ruby_package_ensure = $mysql::params::ruby_package_ensure, + $ruby_package_name = $mysql::params::ruby_package_name, + $ruby_package_provider = $mysql::params::ruby_package_provider, + $client_dev_package_ensure = $mysql::params::client_dev_package_ensure, + $client_dev_package_name = $mysql::params::client_dev_package_name, + $client_dev_package_provider = $mysql::params::client_dev_package_provider, + $daemon_dev_package_ensure = $mysql::params::daemon_dev_package_ensure, + $daemon_dev_package_name = $mysql::params::daemon_dev_package_name, + $daemon_dev_package_provider = $mysql::params::daemon_dev_package_provider +) inherits mysql::params { + + case $::osfamily { + 'Archlinux': { + if $java_enable { fail("::mysql::bindings::java cannot be managed by puppet on ${::osfamily} as it is not in official repositories. Please disable java mysql binding.") } + if $perl_enable { include '::mysql::bindings::perl' } + if $php_enable { warning("::mysql::bindings::php does not need to be managed by puppet on ${::osfamily} as it is included in mysql package by default.") } + if $python_enable { include '::mysql::bindings::python' } + if $ruby_enable { fail("::mysql::bindings::ruby cannot be managed by puppet on ${::osfamily} as it is not in official repositories. Please disable ruby mysql binding.") } + } + + default: { + if $java_enable { include '::mysql::bindings::java' } + if $perl_enable { include '::mysql::bindings::perl' } + if $php_enable { include '::mysql::bindings::php' } + if $python_enable { include '::mysql::bindings::python' } + if $ruby_enable { include '::mysql::bindings::ruby' } + } + } + + if $client_dev { include '::mysql::bindings::client_dev' } + if $daemon_dev { include '::mysql::bindings::daemon_dev' } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/client_dev.pp b/environments/prod/modules/mysql/manifests/bindings/client_dev.pp new file mode 100644 index 0000000..ee3c2fe --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/client_dev.pp @@ -0,0 +1,15 @@ +# Private class +class mysql::bindings::client_dev { + + if $mysql::bindings::client_dev_package_name { + package { 'mysql-client_dev': + ensure => $mysql::bindings::client_dev_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::client_dev_package_name, + provider => $mysql::bindings::client_dev_package_provider, + } + } else { + warning("No MySQL client development package configured for ${::operatingsystem}.") + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/daemon_dev.pp b/environments/prod/modules/mysql/manifests/bindings/daemon_dev.pp new file mode 100644 index 0000000..0515358 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/daemon_dev.pp @@ -0,0 +1,15 @@ +# Private class +class mysql::bindings::daemon_dev { + + if $mysql::bindings::daemon_dev_package_name { + package { 'mysql-daemon_dev': + ensure => $mysql::bindings::daemon_dev_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::daemon_dev_package_name, + provider => $mysql::bindings::daemon_dev_package_provider, + } + } else { + warning("No MySQL daemon development package configured for ${::operatingsystem}.") + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/java.pp b/environments/prod/modules/mysql/manifests/bindings/java.pp new file mode 100644 index 0000000..db93ac2 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/java.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::java { + + package { 'mysql-connector-java': + ensure => $mysql::bindings::java_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::java_package_name, + provider => $mysql::bindings::java_package_provider, + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/perl.pp b/environments/prod/modules/mysql/manifests/bindings/perl.pp new file mode 100644 index 0000000..99d429c --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/perl.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::perl { + + package{ 'perl_mysql': + ensure => $mysql::bindings::perl_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::perl_package_name, + provider => $mysql::bindings::perl_package_provider, + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/php.pp b/environments/prod/modules/mysql/manifests/bindings/php.pp new file mode 100644 index 0000000..9af7069 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/php.pp @@ -0,0 +1,11 @@ +# Private class: See README.md +class mysql::bindings::php { + + package { 'php-mysql': + ensure => $mysql::bindings::php_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::php_package_name, + provider => $mysql::bindings::php_package_provider, + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/python.pp b/environments/prod/modules/mysql/manifests/bindings/python.pp new file mode 100644 index 0000000..a44c8fa --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/python.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::python { + + package { 'python-mysqldb': + ensure => $mysql::bindings::python_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::python_package_name, + provider => $mysql::bindings::python_package_provider, + } + +} diff --git a/environments/prod/modules/mysql/manifests/bindings/ruby.pp b/environments/prod/modules/mysql/manifests/bindings/ruby.pp new file mode 100644 index 0000000..d431efe --- /dev/null +++ b/environments/prod/modules/mysql/manifests/bindings/ruby.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::ruby { + + package{ 'ruby_mysql': + ensure => $mysql::bindings::ruby_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::ruby_package_name, + provider => $mysql::bindings::ruby_package_provider, + } + +} diff --git a/environments/prod/modules/mysql/manifests/client.pp b/environments/prod/modules/mysql/manifests/client.pp new file mode 100644 index 0000000..3d5d706 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/client.pp @@ -0,0 +1,29 @@ +# +class mysql::client ( + $bindings_enable = $mysql::params::bindings_enable, + $install_options = undef, + $package_ensure = $mysql::params::client_package_ensure, + $package_manage = $mysql::params::client_package_manage, + $package_name = $mysql::params::client_package_name, +) inherits mysql::params { + + include '::mysql::client::install' + + if $bindings_enable { + class { 'mysql::bindings': + java_enable => true, + perl_enable => true, + php_enable => true, + python_enable => true, + ruby_enable => true, + } + } + + + # Anchor pattern workaround to avoid resources of mysql::client::install to + # "float off" outside mysql::client + anchor { 'mysql::client::start': } -> + Class['mysql::client::install'] -> + anchor { 'mysql::client::end': } + +} diff --git a/environments/prod/modules/mysql/manifests/client/install.pp b/environments/prod/modules/mysql/manifests/client/install.pp new file mode 100644 index 0000000..26e5ec2 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/client/install.pp @@ -0,0 +1,14 @@ +# See README.md. +class mysql::client::install { + + if $mysql::client::package_manage { + + package { 'mysql_client': + ensure => $mysql::client::package_ensure, + install_options => $mysql::client::install_options, + name => $mysql::client::package_name, + } + + } + +} diff --git a/environments/prod/modules/mysql/manifests/db.pp b/environments/prod/modules/mysql/manifests/db.pp new file mode 100644 index 0000000..f5b2221 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/db.pp @@ -0,0 +1,72 @@ +# See README.md for details. +define mysql::db ( + $user, + $password, + $dbname = $name, + $charset = 'utf8', + $collate = 'utf8_general_ci', + $host = 'localhost', + $grant = 'ALL', + $sql = undef, + $enforce_sql = false, + $ensure = 'present', + $import_timeout = 300, + $import_cat_cmd = 'cat', +) { + #input validation + validate_re($ensure, '^(present|absent)$', + "${ensure} is not supported for ensure. Allowed values are 'present' and 'absent'.") + $table = "${dbname}.*" + + if !(is_array($sql) or is_string($sql)) { + fail('$sql must be either a string or an array.') + } + + $sql_inputs = join([$sql], ' ') + + include '::mysql::client' + + $db_resource = { + ensure => $ensure, + charset => $charset, + collate => $collate, + provider => 'mysql', + require => [ Class['mysql::client'] ], + } + ensure_resource('mysql_database', $dbname, $db_resource) + + $user_resource = { + ensure => $ensure, + password_hash => mysql_password($password), + provider => 'mysql', + } + ensure_resource('mysql_user', "${user}@${host}", $user_resource) + + if $ensure == 'present' { + mysql_grant { "${user}@${host}/${table}": + privileges => $grant, + provider => 'mysql', + user => "${user}@${host}", + table => $table, + require => [ + Mysql_database[$dbname], + Mysql_user["${user}@${host}"], + ], + } + + $refresh = ! $enforce_sql + + if $sql { + exec{ "${dbname}-import": + command => "${import_cat_cmd} ${sql_inputs} | mysql ${dbname}", + logoutput => true, + environment => "HOME=${::root_home}", + refreshonly => $refresh, + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin', + require => Mysql_grant["${user}@${host}/${table}"], + subscribe => Mysql_database[$dbname], + timeout => $import_timeout, + } + } + } +} diff --git a/environments/prod/modules/mysql/manifests/params.pp b/environments/prod/modules/mysql/manifests/params.pp new file mode 100644 index 0000000..8f65b8f --- /dev/null +++ b/environments/prod/modules/mysql/manifests/params.pp @@ -0,0 +1,450 @@ +# Private class: See README.md. +class mysql::params { + + $manage_config_file = true + $purge_conf_dir = false + $restart = false + $root_password = 'UNSET' + $install_secret_file = '/.mysql_secret' + $server_package_ensure = 'present' + $server_package_manage = true + $server_service_manage = true + $server_service_enabled = true + $client_package_ensure = 'present' + $client_package_manage = true + $create_root_user = true + $create_root_my_cnf = true + # mysql::bindings + $bindings_enable = false + $java_package_ensure = 'present' + $java_package_provider = undef + $perl_package_ensure = 'present' + $perl_package_provider = undef + $php_package_ensure = 'present' + $php_package_provider = undef + $python_package_ensure = 'present' + $python_package_provider = undef + $ruby_package_ensure = 'present' + $ruby_package_provider = undef + $client_dev_package_ensure = 'present' + $client_dev_package_provider = undef + $daemon_dev_package_ensure = 'present' + $daemon_dev_package_provider = undef + $xtrabackup_package_name = 'percona-xtrabackup' + + + case $::osfamily { + 'RedHat': { + case $::operatingsystem { + 'Fedora': { + if versioncmp($::operatingsystemrelease, '19') >= 0 or $::operatingsystemrelease == 'Rawhide' { + $provider = 'mariadb' + } else { + $provider = 'mysql' + } + } + /^(RedHat|CentOS|Scientific|OracleLinux)$/: { + if versioncmp($::operatingsystemmajrelease, '7') >= 0 { + $provider = 'mariadb' + } else { + $provider = 'mysql' + } + } + default: { + $provider = 'mysql' + } + } + + if $provider == 'mariadb' { + $client_package_name = 'mariadb' + $server_package_name = 'mariadb-server' + $server_service_name = 'mariadb' + $log_error = '/var/log/mariadb/mariadb.log' + $config_file = '/etc/my.cnf.d/server.cnf' + # mariadb package by default has !includedir set in my.cnf to /etc/my.cnf.d + $includedir = undef + $pidfile = '/var/run/mariadb/mariadb.pid' + $daemon_dev_package_name = 'mariadb-devel' + } else { + $client_package_name = 'mysql' + $server_package_name = 'mysql-server' + $server_service_name = 'mysqld' + $log_error = '/var/log/mysqld.log' + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $pidfile = '/var/run/mysqld/mysqld.pid' + $daemon_dev_package_name = 'mysql-devel' + } + + $basedir = '/usr' + $datadir = '/var/lib/mysql' + $root_group = 'root' + $mysql_group = 'mysql' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-MySQL' + $php_package_name = 'php-mysql' + $python_package_name = 'MySQL-python' + $ruby_package_name = 'ruby-mysql' + $client_dev_package_name = undef + } + + 'Suse': { + case $::operatingsystem { + 'OpenSuSE': { + if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb' + # First service start fails if this is set. Runs fine without + # it being set, in any case. Leaving it as-is for the mysql. + $basedir = undef + } else { + $client_package_name = 'mysql-community-server-client' + $server_package_name = 'mysql-community-server' + $basedir = '/usr' + } + } + 'SLES','SLED': { + if versioncmp($::operatingsystemrelease, '12') >= 0 { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb' + $basedir = undef + } else { + $client_package_name = 'mysql-client' + $server_package_name = 'mysql' + $basedir = '/usr' + } + } + default: { + fail("Unsupported platform: puppetlabs-${module_name} currently doesn't support ${::operatingsystem}") + } + } + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $datadir = '/var/lib/mysql' + $log_error = $::operatingsystem ? { + /OpenSuSE/ => '/var/log/mysql/mysqld.log', + /(SLES|SLED)/ => '/var/log/mysqld.log', + } + $pidfile = $::operatingsystem ? { + /OpenSuSE/ => '/var/run/mysql/mysqld.pid', + /(SLES|SLED)/ => '/var/lib/mysql/mysqld.pid', + } + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysql' + + if $::operatingsystem =~ /(SLES|SLED)/ { + if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { + $socket = '/run/mysql/mysql.sock' + } else { + $socket = '/var/lib/mysql/mysql.sock' + } + } else { + $socket = '/var/run/mysql/mysql.sock' + } + + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-mysql' + $php_package_name = 'apache2-mod_php53' + $python_package_name = 'python-mysql' + $ruby_package_name = $::operatingsystem ? { + /OpenSuSE/ => 'rubygem-mysql', + /(SLES|SLED)/ => 'ruby-mysql', + } + $client_dev_package_name = 'libmysqlclient-devel' + $daemon_dev_package_name = 'mysql-devel' + } + + 'Debian': { + $client_package_name = 'mysql-client' + $server_package_name = 'mysql-server' + + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $includedir = '/etc/mysql/conf.d' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysql/error.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'adm' + $server_service_name = 'mysql' + $socket = '/var/run/mysqld/mysqld.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'libmysql-java' + $perl_package_name = 'libdbd-mysql-perl' + $php_package_name = $::lsbdistcodename ? { + 'xenial' => 'php-mysql', + default => 'php5-mysql', + } + $python_package_name = 'python-mysqldb' + $ruby_package_name = $::lsbdistcodename ? { + 'trusty' => 'ruby-mysql', + 'jessie' => 'ruby-mysql', + 'xenial' => 'ruby-mysql', + default => 'libmysql-ruby', + } + $client_dev_package_name = 'libmysqlclient-dev' + $daemon_dev_package_name = 'libmysqld-dev' + } + + 'Archlinux': { + $client_package_name = 'mariadb-clients' + $server_package_name = 'mariadb' + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysqld' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-dbd-mysql' + $php_package_name = undef + $python_package_name = 'mysql-python' + $ruby_package_name = 'mysql-ruby' + } + + 'Gentoo': { + $client_package_name = 'virtual/mysql' + $server_package_name = 'virtual/mysql' + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysql/mysqld.err' + $pidfile = '/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysql' + $socket = '/run/mysqld/mysqld.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'dev-java/jdbc-mysql' + $perl_package_name = 'dev-perl/DBD-mysql' + $php_package_name = undef + $python_package_name = 'dev-python/mysql-python' + $ruby_package_name = 'dev-ruby/mysql-ruby' + } + + 'FreeBSD': { + $client_package_name = 'databases/mysql56-client' + $server_package_name = 'databases/mysql56-server' + $basedir = '/usr/local' + $config_file = '/usr/local/etc/my.cnf' + $includedir = '/usr/local/etc/my.cnf.d' + $datadir = '/var/db/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysql.pid' + $root_group = 'wheel' + $mysql_group = 'mysql' + $server_service_name = 'mysql-server' + $socket = '/var/db/mysql/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'databases/mysql-connector-java' + $perl_package_name = 'p5-DBD-mysql' + $php_package_name = 'php5-mysql' + $python_package_name = 'databases/py-MySQLdb' + $ruby_package_name = 'databases/ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + 'OpenBSD': { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb-server' + $basedir = '/usr/local' + $config_file = '/etc/my.cnf' + $includedir = undef + $datadir = '/var/mysql' + $log_error = "/var/mysql/${::hostname}.err" + $pidfile = '/var/mysql/mysql.pid' + $root_group = 'wheel' + $mysql_group = '_mysql' + $server_service_name = 'mysqld' + $socket = '/var/run/mysql/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = undef + $perl_package_name = 'p5-DBD-mysql' + $php_package_name = 'php-mysql' + $python_package_name = 'py-mysql' + $ruby_package_name = 'ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + 'Solaris': { + $client_package_name = 'database/mysql-55/client' + $server_package_name = 'database/mysql-55' + $basedir = undef + $config_file = '/etc/mysql/5.5/my.cnf' + $datadir = '/var/mysql/5.5/data' + $log_error = "/var/mysql/5.5/data/${::hostname}.err" + $pidfile = "/var/mysql/5.5/data/${::hostname}.pid" + $root_group = 'bin' + $server_service_name = 'application/database/mysql:version_55' + $socket = '/tmp/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = undef + $perl_package_name = undef + $php_package_name = 'web/php-53/extension/php-mysql' + $python_package_name = 'library/python/python-mysql' + $ruby_package_name = undef + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + default: { + case $::operatingsystem { + 'Amazon': { + $client_package_name = 'mysql' + $server_package_name = 'mysql-server' + $basedir = '/usr' + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysqld' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-MySQL' + $php_package_name = 'php-mysql' + $python_package_name = 'MySQL-python' + $ruby_package_name = 'ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + default: { + fail("Unsupported platform: puppetlabs-${module_name} currently doesn't support ${::osfamily} or ${::operatingsystem}") + } + } + } + } + + case $::operatingsystem { + 'Ubuntu': { + if versioncmp($::operatingsystemmajrelease, '14.10') > 0 { + $server_service_provider = 'systemd' + } else { + $server_service_provider = 'upstart' + } + } + default: { + $server_service_provider = undef + } + } + + $default_options = { + 'client' => { + 'port' => '3306', + 'socket' => $mysql::params::socket, + }, + 'mysqld_safe' => { + 'nice' => '0', + 'log-error' => $mysql::params::log_error, + 'socket' => $mysql::params::socket, + }, + 'mysqld-5.0' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.1' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.5' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.6' => { + 'myisam-recover-options' => 'BACKUP', + }, + 'mysqld-5.7' => { + 'myisam-recover-options' => 'BACKUP', + }, + 'mysqld' => { + 'basedir' => $mysql::params::basedir, + 'bind-address' => '127.0.0.1', + 'datadir' => $mysql::params::datadir, + 'expire_logs_days' => '10', + 'key_buffer_size' => '16M', + 'log-error' => $mysql::params::log_error, + 'max_allowed_packet' => '16M', + 'max_binlog_size' => '100M', + 'max_connections' => '151', + 'pid-file' => $mysql::params::pidfile, + 'port' => '3306', + 'query_cache_limit' => '1M', + 'query_cache_size' => '16M', + 'skip-external-locking' => true, + 'socket' => $mysql::params::socket, + 'ssl' => false, + 'ssl-ca' => $mysql::params::ssl_ca, + 'ssl-cert' => $mysql::params::ssl_cert, + 'ssl-key' => $mysql::params::ssl_key, + 'ssl-disable' => false, + 'thread_cache_size' => '8', + 'thread_stack' => '256K', + 'tmpdir' => $mysql::params::tmpdir, + 'user' => 'mysql', + }, + 'mysqldump' => { + 'max_allowed_packet' => '16M', + 'quick' => true, + 'quote-names' => true, + }, + 'isamchk' => { + 'key_buffer_size' => '16M', + }, + } + + ## Additional graceful failures + if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '4' and $::operatingsystem != 'Amazon' { + fail("Unsupported platform: puppetlabs-${module_name} only supports RedHat 5.0 and beyond") + } +} diff --git a/environments/prod/modules/mysql/manifests/server.pp b/environments/prod/modules/mysql/manifests/server.pp new file mode 100644 index 0000000..e910c21 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server.pp @@ -0,0 +1,89 @@ +# Class: mysql::server: See README.md for documentation. +class mysql::server ( + $config_file = $mysql::params::config_file, + $includedir = $mysql::params::includedir, + $install_options = undef, + $install_secret_file = $mysql::params::install_secret_file, + $manage_config_file = $mysql::params::manage_config_file, + $override_options = {}, + $package_ensure = $mysql::params::server_package_ensure, + $package_manage = $mysql::params::server_package_manage, + $package_name = $mysql::params::server_package_name, + $purge_conf_dir = $mysql::params::purge_conf_dir, + $remove_default_accounts = false, + $restart = $mysql::params::restart, + $root_group = $mysql::params::root_group, + $mysql_group = $mysql::params::mysql_group, + $root_password = $mysql::params::root_password, + $service_enabled = $mysql::params::server_service_enabled, + $service_manage = $mysql::params::server_service_manage, + $service_name = $mysql::params::server_service_name, + $service_provider = $mysql::params::server_service_provider, + $create_root_user = $mysql::params::create_root_user, + $create_root_my_cnf = $mysql::params::create_root_my_cnf, + $users = {}, + $grants = {}, + $databases = {}, + + # Deprecated parameters + $enabled = undef, + $manage_service = undef, + $old_root_password = undef +) inherits mysql::params { + + # Deprecated parameters. + if $enabled { + crit('This parameter has been renamed to service_enabled.') + $real_service_enabled = $enabled + } else { + $real_service_enabled = $service_enabled + } + if $manage_service { + crit('This parameter has been renamed to service_manage.') + $real_service_manage = $manage_service + } else { + $real_service_manage = $service_manage + } + if $old_root_password { + warning('old_root_password is no longer used and will be removed in a future release') + } + + # Create a merged together set of options. Rightmost hashes win over left. + $options = mysql_deepmerge($mysql::params::default_options, $override_options) + + Class['mysql::server::root_password'] -> Mysql::Db <| |> + + include '::mysql::server::config' + include '::mysql::server::install' + include '::mysql::server::binarylog' + include '::mysql::server::installdb' + include '::mysql::server::service' + include '::mysql::server::root_password' + include '::mysql::server::providers' + + if $remove_default_accounts { + class { '::mysql::server::account_security': + require => Anchor['mysql::server::end'], + } + } + + anchor { 'mysql::server::start': } + anchor { 'mysql::server::end': } + + if $restart { + Class['mysql::server::config'] ~> + Class['mysql::server::service'] + } + + Anchor['mysql::server::start'] -> + Class['mysql::server::config'] -> + Class['mysql::server::install'] -> + Class['mysql::server::binarylog'] -> + Class['mysql::server::installdb'] -> + Class['mysql::server::service'] -> + Class['mysql::server::root_password'] -> + Class['mysql::server::providers'] -> + Anchor['mysql::server::end'] + + +} diff --git a/environments/prod/modules/mysql/manifests/server/account_security.pp b/environments/prod/modules/mysql/manifests/server/account_security.pp new file mode 100644 index 0000000..3fcdd61 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/account_security.pp @@ -0,0 +1,39 @@ +# See README.md. +class mysql::server::account_security { + mysql_user { + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + '@%']: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + if ($::fqdn != 'localhost.localdomain') { + mysql_user { + [ 'root@localhost.localdomain', + '@localhost.localdomain']: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + if ($::fqdn and $::fqdn != 'localhost') { + mysql_user { + [ "root@${::fqdn}", + "@${::fqdn}"]: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + if ($::fqdn != $::hostname) { + if ($::hostname != 'localhost') { + mysql_user { ["root@${::hostname}", "@${::hostname}"]: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + } + mysql_database { 'test': + ensure => 'absent', + require => Anchor['mysql::server::end'], + } +} diff --git a/environments/prod/modules/mysql/manifests/server/backup.pp b/environments/prod/modules/mysql/manifests/server/backup.pp new file mode 100644 index 0000000..d31a880 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/backup.pp @@ -0,0 +1,55 @@ +# See README.me for usage. +class mysql::server::backup ( + $backupuser = undef, + $backuppassword = undef, + $backupdir = undef, + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = 'root', + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_routines = false, + $include_triggers = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', + $provider = 'mysqldump', + $maxallowedpacket = '1M', +) { + + if $prescript and $provider =~ /(mysqldump|mysqlbackup)/ { + warning("The \$prescript option is not currently implemented for the ${provider} backup provider.") + } + + create_resources('class', { + "mysql::backup::${provider}" => { + 'backupuser' => $backupuser, + 'backuppassword' => $backuppassword, + 'backupdir' => $backupdir, + 'backupdirmode' => $backupdirmode, + 'backupdirowner' => $backupdirowner, + 'backupdirgroup' => $backupdirgroup, + 'backupcompress' => $backupcompress, + 'backuprotate' => $backuprotate, + 'ignore_events' => $ignore_events, + 'delete_before_dump' => $delete_before_dump, + 'backupdatabases' => $backupdatabases, + 'file_per_database' => $file_per_database, + 'include_routines' => $include_routines, + 'include_triggers' => $include_triggers, + 'ensure' => $ensure, + 'time' => $time, + 'prescript' => $prescript, + 'postscript' => $postscript, + 'execpath' => $execpath, + 'maxallowedpacket' => $maxallowedpacket, + } + }) + +} diff --git a/environments/prod/modules/mysql/manifests/server/binarylog.pp b/environments/prod/modules/mysql/manifests/server/binarylog.pp new file mode 100644 index 0000000..459915d --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/binarylog.pp @@ -0,0 +1,22 @@ +# Binary log configuration requires the mysql user to be present. This must be done after package install +class mysql::server::binarylog { + + $options = $mysql::server::options + $includedir = $mysql::server::includedir + + $logbin = pick($options['mysqld']['log-bin'], $options['mysqld']['log_bin'], false) + + if $logbin { + $logbindir = mysql_dirname($logbin) + + #Stop puppet from managing directory if just a filename/prefix is specified + if $logbindir != '.' { + file { $logbindir: + ensure => directory, + mode => '0755', + owner => $options['mysqld']['user'], + group => $options['mysqld']['user'], + } + } + } +} diff --git a/environments/prod/modules/mysql/manifests/server/config.pp b/environments/prod/modules/mysql/manifests/server/config.pp new file mode 100644 index 0000000..efb4940 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/config.pp @@ -0,0 +1,56 @@ +# See README.me for options. +class mysql::server::config { + + $options = $mysql::server::options + $includedir = $mysql::server::includedir + + File { + owner => 'root', + group => $mysql::server::root_group, + mode => '0400', + } + + if $includedir and $includedir != '' { + file { $includedir: + ensure => directory, + mode => '0755', + recurse => $mysql::server::purge_conf_dir, + purge => $mysql::server::purge_conf_dir, + } + + # on some systems this is /etc/my.cnf.d, while Debian has /etc/mysql/conf.d and FreeBSD something in /usr/local. For the latter systems, + # managing this basedir is also required, to have it available before the package is installed. + $includeparentdir = mysql_dirname($includedir) + if $includeparentdir != '/' and $includeparentdir != '/etc' { + file { $includeparentdir: + ensure => directory, + mode => '0755', + } + } + } + + if $mysql::server::manage_config_file { + file { 'mysql-config-file': + path => $mysql::server::config_file, + content => template('mysql/my.cnf.erb'), + mode => '0644', + selinux_ignore_defaults => true, + } + + # on mariadb systems, $includedir is not defined, but /etc/my.cnf.d has + # to be managed to place the server.cnf there + $configparentdir = mysql_dirname($mysql::server::config_file) + if $configparentdir != '/' and $configparentdir != '/etc' and $configparentdir != $includedir and $configparentdir != mysql_dirname($includedir) { + file { $configparentdir: + ensure => directory, + mode => '0755', + } + } + } + + if $options['mysqld']['ssl-disable'] { + notify {'ssl-disable': + message =>'Disabling SSL is evil! You should never ever do this except if you are forced to use a mysql version compiled without SSL support' + } + } +} diff --git a/environments/prod/modules/mysql/manifests/server/install.pp b/environments/prod/modules/mysql/manifests/server/install.pp new file mode 100644 index 0000000..3b9601d --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/install.pp @@ -0,0 +1,13 @@ +# +class mysql::server::install { + + if $mysql::server::package_manage { + + package { 'mysql-server': + ensure => $mysql::server::package_ensure, + install_options => $mysql::server::install_options, + name => $mysql::server::package_name, + } + } + +} diff --git a/environments/prod/modules/mysql/manifests/server/installdb.pp b/environments/prod/modules/mysql/manifests/server/installdb.pp new file mode 100644 index 0000000..5b419f2 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/installdb.pp @@ -0,0 +1,46 @@ +# +class mysql::server::installdb { + $options = $mysql::server::options + + if $mysql::server::package_manage { + + # Build the initial databases. + $mysqluser = $mysql::server::options['mysqld']['user'] + $datadir = $mysql::server::options['mysqld']['datadir'] + $basedir = $mysql::server::options['mysqld']['basedir'] + $config_file = $mysql::server::config_file + $log_error = $mysql::server::options['mysqld']['log-error'] + + if $mysql::server::manage_config_file and $config_file != $mysql::params::config_file { + $_config_file=$config_file + } else { + $_config_file=undef + } + + if $options['mysqld']['log-error'] { + file { $options['mysqld']['log-error']: + ensure => present, + owner => $mysqluser, + group => $::mysql::server::mysql_group, + mode => 'u+rw', + before => Mysql_datadir[ $datadir ], + } + } + + mysql_datadir { $datadir: + ensure => 'present', + datadir => $datadir, + basedir => $basedir, + user => $mysqluser, + log_error => $log_error, + defaults_extra_file => $_config_file, + } + + if $mysql::server::restart { + Mysql_datadir[$datadir] { + notify => Class['mysql::server::service'], + } + } + } + +} diff --git a/environments/prod/modules/mysql/manifests/server/monitor.pp b/environments/prod/modules/mysql/manifests/server/monitor.pp new file mode 100644 index 0000000..6b18609 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/monitor.pp @@ -0,0 +1,24 @@ +#This is a helper class to add a monitoring user to the database +class mysql::server::monitor ( + $mysql_monitor_username = '', + $mysql_monitor_password = '', + $mysql_monitor_hostname = '' +) { + + Anchor['mysql::server::end'] -> Class['mysql::server::monitor'] + + mysql_user { "${mysql_monitor_username}@${mysql_monitor_hostname}": + ensure => present, + password_hash => mysql_password($mysql_monitor_password), + require => Class['mysql::server::service'], + } + + mysql_grant { "${mysql_monitor_username}@${mysql_monitor_hostname}/*.*": + ensure => present, + user => "${mysql_monitor_username}@${mysql_monitor_hostname}", + table => '*.*', + privileges => [ 'PROCESS', 'SUPER' ], + require => Mysql_user["${mysql_monitor_username}@${mysql_monitor_hostname}"], + } + +} diff --git a/environments/prod/modules/mysql/manifests/server/mysqltuner.pp b/environments/prod/modules/mysql/manifests/server/mysqltuner.pp new file mode 100644 index 0000000..ff1d563 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/mysqltuner.pp @@ -0,0 +1,52 @@ +# +class mysql::server::mysqltuner( + $ensure = 'present', + $version = 'v1.3.0', + $source = undef, +) { + + if $source { + $_version = $source + $_source = $source + } else { + $_version = $version + $_source = "https://github.com/major/MySQLTuner-perl/raw/${version}/mysqltuner.pl" + } + + if $ensure == 'present' { + # $::puppetversion doesn't exist in puppet 4.x so would break strict + # variables + if ! $::settings::strict_variables { + $_puppetversion = $::puppetversion + } else { + # defined only works with puppet >= 3.5.0, so don't use it unless we're + # actually using strict variables + $_puppetversion = defined('$puppetversion') ? { + true => $::puppetversion, + default => undef, + } + } + # see https://tickets.puppetlabs.com/browse/ENTERPRISE-258 + if $_puppetversion and $_puppetversion =~ /Puppet Enterprise/ and versioncmp($_puppetversion, '3.8.0') < 0 { + class { '::staging': + path => '/opt/mysql_staging', + } + } else { + class { '::staging': } + } + + staging::file { "mysqltuner-${_version}": + source => $_source, + } + file { '/usr/local/bin/mysqltuner': + ensure => $ensure, + mode => '0550', + source => "${::staging::path}/mysql/mysqltuner-${_version}", + require => Staging::File["mysqltuner-${_version}"], + } + } else { + file { '/usr/local/bin/mysqltuner': + ensure => $ensure, + } + } +} diff --git a/environments/prod/modules/mysql/manifests/server/providers.pp b/environments/prod/modules/mysql/manifests/server/providers.pp new file mode 100644 index 0000000..b651172 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/providers.pp @@ -0,0 +1,8 @@ +# Convenience class to call each of the three providers with the corresponding +# hashes provided in mysql::server. +# See README.md for details. +class mysql::server::providers { + create_resources('mysql_user', $mysql::server::users) + create_resources('mysql_grant', $mysql::server::grants) + create_resources('mysql_database', $mysql::server::databases) +} diff --git a/environments/prod/modules/mysql/manifests/server/root_password.pp b/environments/prod/modules/mysql/manifests/server/root_password.pp new file mode 100644 index 0000000..9ebc103 --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/root_password.pp @@ -0,0 +1,46 @@ +# +class mysql::server::root_password { + + $options = $mysql::server::options + $secret_file = $mysql::server::install_secret_file + + # New installations of MySQL will configure a default random password for the root user + # with an expiration. No actions can be performed until this password is changed. The + # below exec will remove this default password. If the user has supplied a root + # password it will be set further down with the mysql_user resource. + $rm_pass_cmd = join([ + "mysqladmin -u root --password=\$(grep -o '[^ ]\\+\$' ${secret_file}) password ''", + "rm -f ${secret_file}" + ], ' && ') + exec { 'remove install pass': + command => $rm_pass_cmd, + onlyif => "test -f ${secret_file}", + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + } + + # manage root password if it is set + if $mysql::server::create_root_user == true and $mysql::server::root_password != 'UNSET' { + mysql_user { 'root@localhost': + ensure => present, + password_hash => mysql_password($mysql::server::root_password), + require => Exec['remove install pass'] + } + } + + if $mysql::server::create_root_my_cnf == true and $mysql::server::root_password != 'UNSET' { + file { "${::root_home}/.my.cnf": + content => template('mysql/my.cnf.pass.erb'), + owner => 'root', + mode => '0600', + } + + # show_diff was added with puppet 3.0 + if versioncmp($::puppetversion, '3.0') >= 0 { + File["${::root_home}/.my.cnf"] { show_diff => false } + } + if $mysql::server::create_root_user == true { + Mysql_user['root@localhost'] -> File["${::root_home}/.my.cnf"] + } + } + +} diff --git a/environments/prod/modules/mysql/manifests/server/service.pp b/environments/prod/modules/mysql/manifests/server/service.pp new file mode 100644 index 0000000..3617b4b --- /dev/null +++ b/environments/prod/modules/mysql/manifests/server/service.pp @@ -0,0 +1,58 @@ +# +class mysql::server::service { + $options = $mysql::server::options + + if $mysql::server::real_service_manage { + if $mysql::server::real_service_enabled { + $service_ensure = 'running' + } else { + $service_ensure = 'stopped' + } + } else { + $service_ensure = undef + } + + if $mysql::server::override_options and $mysql::server::override_options['mysqld'] and $mysql::server::override_options['mysqld']['user'] { + $mysqluser = $mysql::server::override_options['mysqld']['user'] + } else { + $mysqluser = $options['mysqld']['user'] + } + + if $mysql::server::real_service_manage { + service { 'mysqld': + ensure => $service_ensure, + name => $mysql::server::service_name, + enable => $mysql::server::real_service_enabled, + provider => $mysql::server::service_provider, + } + + # only establish ordering between service and package if + # we're managing the package. + if $mysql::server::package_manage { + Service['mysqld'] { + require => Package['mysql-server'], + } + } + + # only establish ordering between config file and service if + # we're managing the config file. + if $mysql::server::manage_config_file { + File['mysql-config-file'] -> Service['mysqld'] + } + + if $mysql::server::override_options and $mysql::server::override_options['mysqld'] and $mysql::server::override_options['mysqld']['socket'] { + $mysqlsocket = $mysql::server::override_options['mysqld']['socket'] + } else { + $mysqlsocket = $options['mysqld']['socket'] + } + + exec { 'wait_for_mysql_socket_to_open': + command => "test -S ${mysqlsocket}", + unless => "test -S ${mysqlsocket}", + tries => '3', + try_sleep => '10', + require => Service['mysqld'], + path => '/bin:/usr/bin', + } + } +} diff --git a/environments/prod/modules/mysql/metadata.json b/environments/prod/modules/mysql/metadata.json new file mode 100644 index 0000000..41c8da1 --- /dev/null +++ b/environments/prod/modules/mysql/metadata.json @@ -0,0 +1,80 @@ +{ + "name": "puppetlabs-mysql", + "version": "3.10.0", + "author": "Puppet Labs", + "summary": "Installs, configures, and manages the MySQL service.", + "license": "Apache-2.0", + "source": "git://github.com/puppetlabs/puppetlabs-mysql.git", + "project_page": "http://github.com/puppetlabs/puppetlabs-mysql", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "dependencies": [ + {"name":"puppetlabs/stdlib","version_requirement":">= 3.2.0 < 5.0.0"}, + {"name":"puppet/staging","version_requirement":">= 1.0.1 < 3.0.0"} + ], + "data_provider": null, + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "11 SP1", + "12" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7", + "8" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04", + "16.04" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 3.0.0 < 5.0.0" + } + ], + "description": "MySQL module" +} diff --git a/environments/prod/modules/mysql/spec/acceptance/mysql_backup_spec.rb b/environments/prod/modules/mysql/spec/acceptance/mysql_backup_spec.rb new file mode 100644 index 0000000..88a90cc --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/mysql_backup_spec.rb @@ -0,0 +1,205 @@ +require 'spec_helper_acceptance' +require 'puppet' +require 'puppet/util/package' +require_relative './mysql_helper.rb' + +describe 'mysql::server::backup class' do + context 'should work with no errors' do + it 'when configuring mysql backups' do + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'mysqlbackup.sh' do + it 'should run mysqlbackup.sh with no errors' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("/usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + + it 'should dump all databases to single file' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| + expect(r.stdout).to match(/1/) + expect(r.exit_code).to be_zero + end + end + end + + context 'should create one file per database per run' do + it 'executes mysqlbackup.sh a second time' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('sleep 1') + shell('/usr/local/sbin/mysqlbackup.sh') + end + end + + it 'creates at least one backup tarball' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| + expect(r.stdout).to match(/2/) + expect(r.exit_code).to be_zero + end + end + end + end + end + + context 'with one file per database' do + context 'should work with no errors' do + it 'when configuring mysql backups' do + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + file_per_database => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'mysqlbackup.sh' do + it 'should run mysqlbackup.sh with no errors without root credentials' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + + it 'should create one file per database' do + pre_run + if ! version_is_greater_than('5.7.0') + ['backup1', 'backup2'].each do |database| + shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(/1/) + expect(r.exit_code).to be_zero + end + end + end + end + + context 'should create one file per database per run' do + it 'executes mysqlbackup.sh a second time' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('sleep 1') + shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') + end + end + + it 'has one file per database per run' do + pre_run + if ! version_is_greater_than('5.7.0') + ['backup1', 'backup2'].each do |database| + shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(/2/) + expect(r.exit_code).to be_zero + end + end + end + end + end + end + end + + context 'with triggers and routines' do + it 'when configuring mysql backups with triggers and routines' do + pre_run + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + package { 'bzip2': + ensure => present, + } + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + file_per_database => true, + include_triggers => #{version_is_greater_than('5.1.5')}, + include_routines => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + require => Package['bzip2'], + } + EOS + apply_manifest(pp, :catch_failures => true) + end + + it 'should run mysqlbackup.sh with no errors' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("/usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/acceptance/mysql_db_spec.rb b/environments/prod/modules/mysql/spec/acceptance/mysql_db_spec.rb new file mode 100644 index 0000000..8c57160 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/mysql_db_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper_acceptance' + +describe 'mysql::db define' do + describe 'creating a database' do + let(:pp) do + <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { 'spec1': + user => 'root1', + password => 'password', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show databases;'") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^spec1$/ } + end + end + + describe 'creating a database with post-sql' do + let(:pp) do + <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + file { '/tmp/spec.sql': + ensure => file, + content => 'CREATE TABLE table1 (id int);', + before => Mysql::Db['spec2'], + } + mysql::db { 'spec2': + user => 'root1', + password => 'password', + sql => '/tmp/spec.sql', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show tables;' spec2") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^table1$/ } + end + end + + describe 'creating a database with dbname parameter' do + let(:check_command) { " | grep realdb" } + let(:pp) do + <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + mysql::db { 'spec1': + user => 'root1', + password => 'password', + dbname => 'realdb', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show databases;'") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^realdb$/ } + end + end +end diff --git a/environments/prod/modules/mysql/spec/acceptance/mysql_helper.rb b/environments/prod/modules/mysql/spec/acceptance/mysql_helper.rb new file mode 100644 index 0000000..7a5dd57 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/mysql_helper.rb @@ -0,0 +1,8 @@ + def pre_run + apply_manifest("class { 'mysql::server': root_password => 'password' }", :catch_failures => true) + @mysql_version = (on default, 'mysql --version').output.chomp.match(/\d+\.\d+\.\d+/)[0] + end + + def version_is_greater_than(version) + return Puppet::Util::Package.versioncmp(@mysql_version, version) > 0 + end diff --git a/environments/prod/modules/mysql/spec/acceptance/mysql_server_spec.rb b/environments/prod/modules/mysql/spec/acceptance/mysql_server_spec.rb new file mode 100644 index 0000000..d8e2f3e --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/mysql_server_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper_acceptance' + +describe 'mysql class' do + describe 'advanced config' do + before(:all) do + @tmpdir = default.tmpdir('mysql') + end + let(:pp) do + <<-EOS + class { 'mysql::server': + config_file => '#{@tmpdir}/my.cnf', + includedir => '#{@tmpdir}/include', + manage_config_file => 'true', + override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, + package_ensure => 'present', + purge_conf_dir => 'true', + remove_default_accounts => 'true', + restart => 'true', + root_group => 'root', + root_password => 'test', + service_enabled => 'true', + service_manage => 'true', + users => { + 'someuser@localhost' => { + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', + password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', + }}, + grants => { + 'someuser@localhost/somedb.*' => { + ensure => 'present', + options => ['GRANT'], + privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], + table => 'somedb.*', + user => 'someuser@localhost', + }, + }, + databases => { + 'somedb' => { + ensure => 'present', + charset => 'utf8', + }, + } + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + describe 'minimal config' do + before(:all) do + @tmpdir = default.tmpdir('mysql') + end + let(:pp) do + <<-EOS + class { 'mysql::server': + config_file => '#{@tmpdir}/my.cnf', + includedir => '#{@tmpdir}/include', + manage_config_file => 'false', + override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, + package_ensure => 'present', + purge_conf_dir => 'false', + remove_default_accounts => 'false', + restart => 'false', + root_group => 'root', + root_password => 'test', + service_enabled => 'false', + service_manage => 'false', + users => {}, + grants => {}, + databases => {}, + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + describe 'syslog configuration' do + let(:pp) do + <<-EOS + class { 'mysql::server': + override_options => { 'mysqld' => { 'log-error' => undef }, 'mysqld_safe' => { 'log-error' => false, 'syslog' => true }}, + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + context 'when changing the password' do + let(:password) { 'THE NEW SECRET' } + let(:pp) { "class { 'mysql::server': root_password => '#{password}' }" } + + it 'should not display the password' do + result = apply_manifest(pp, :catch_failures => true) + # this does not actually prove anything, as show_diff in the puppet config defaults to false. + expect(result.stdout).not_to match /#{password}/ + end + + it_behaves_like "a idempotent resource" + end +end diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml new file mode 100644 index 0000000..5eebdef --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-7-x64: + roles: + - agent + - default + platform: el-7-x86_64 + hypervisor: vagrant + box: puppetlabs/centos-7.2-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml new file mode 100644 index 0000000..fef6e63 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + debian-8-x64: + roles: + - agent + - default + platform: debian-8-amd64 + hypervisor: vagrant + box: puppetlabs/debian-8.2-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/default.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..dba339c --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/default.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-1404-x64: + roles: + - agent + - default + platform: ubuntu-14.04-amd64 + hypervisor: vagrant + box: puppetlabs/ubuntu-14.04-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml new file mode 100644 index 0000000..a3333aa --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-7-x64: + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: '["/usr/sbin/init"]' + # install various tools required to get the image up to usable levels + docker_image_commands: + - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml new file mode 100644 index 0000000..df5c319 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml @@ -0,0 +1,11 @@ +HOSTS: + debian-8-x64: + platform: debian-8-amd64 + hypervisor: docker + image: debian:8 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml new file mode 100644 index 0000000..b1efa58 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml @@ -0,0 +1,12 @@ +HOSTS: + ubuntu-1404-x64: + platform: ubuntu-14.04-amd64 + hypervisor: docker + image: ubuntu:14.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + # ensure that upstart is booting correctly in the container + - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/mysql/spec/acceptance/types/mysql_database_spec.rb b/environments/prod/modules/mysql/spec/acceptance/types/mysql_database_spec.rb new file mode 100644 index 0000000..b19026d --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/types/mysql_database_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper_acceptance' + +describe 'mysql_database' do + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'creating database' do + it 'should work without errors' do + pp = <<-EOS + mysql_database { 'spec_db': + ensure => present, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the database' do + shell("mysql -NBe \"SHOW DATABASES LIKE 'spec_db'\"") do |r| + expect(r.stdout).to match(/^spec_db$/) + expect(r.stderr).to be_empty + end + end + end + + describe 'charset and collate' do + it 'should create two db of different types idempotently' do + pp = <<-EOS + mysql_database { 'spec_latin1': + charset => 'latin1', + collate => 'latin1_swedish_ci', + } + mysql_database { 'spec_utf8': + charset => 'utf8', + collate => 'utf8_general_ci', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should find latin1 db' do + shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_latin1") do |r| + expect(r.stdout).to match(/^character_set_database\tlatin1\ncollation_database\tlatin1_swedish_ci$/) + expect(r.stderr).to be_empty + end + end + + it 'should find utf8 db' do + shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_utf8") do |r| + expect(r.stdout).to match(/^character_set_database\tutf8\ncollation_database\tutf8_general_ci$/) + expect(r.stderr).to be_empty + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb b/environments/prod/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb new file mode 100644 index 0000000..ccf2b27 --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb @@ -0,0 +1,578 @@ +require 'spec_helper_acceptance' +require 'puppet' +require 'puppet/util/package' +require_relative '../mysql_helper.rb' + +describe 'mysql_grant' do + before(:all) do + pp = <<-EOS + class { 'mysql::server': + root_password => 'password', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe 'missing privileges for user' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test1@tester': + ensure => present, + } + mysql_grant { 'test1@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test1@tester', + require => Mysql_user['test1@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/privileges parameter is required/) + end + + it 'should not find the user' do + expect(shell("mysql -NBe \"SHOW GRANTS FOR test1@tester\"", { :acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'test1' on host 'tester'/) + end + end + + describe 'missing table for user' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'atest@tester': + ensure => present, + } + mysql_grant { 'atest@tester/test.*': + ensure => 'present', + user => 'atest@tester', + privileges => ['ALL'], + require => Mysql_user['atest@tester'], + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + + it 'should not find the user' do + expect(shell("mysql -NBe \"SHOW GRANTS FOR atest@tester\"", {:acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'atest' on host 'tester'/) + end + end + + describe 'adding privileges' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test2@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test2@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE.*TO 'test2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding privileges with special character in name' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test-2@tester': + ensure => present, + } + mysql_grant { 'test-2@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test-2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test-2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test-2'@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE.*TO 'test-2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding privileges with invalid name' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test': + ensure => 'present', + table => 'test.*', + user => 'test2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test2@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/name must match user and table parameters/) + end + end + + describe 'adding option' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test3@tester': + ensure => present, + } + mysql_grant { 'test3@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test3@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test3@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test3@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE ON `test`.* TO 'test3'@'tester' WITH GRANT OPTION$/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding all privileges without table' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test4@tester': + ensure => present, + } + mysql_grant { 'test4@tester/test.*': + ensure => 'present', + user => 'test4@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE', 'ALL'], + require => Mysql_user['test4@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/table parameter is required./) + end + end + + describe 'adding all privileges' do + it 'should only try to apply ALL' do + pp = <<-EOS + mysql_user { 'test4@tester': + ensure => present, + } + mysql_grant { 'test4@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test4@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE', 'ALL'], + require => Mysql_user['test4@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test4@tester\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test4'@'tester' WITH GRANT OPTION/) + expect(r.stderr).to be_empty + end + end + end + + # Test combinations of user@host to ensure all cases work. + describe 'short hostname' do + it 'should apply' do + pp = <<-EOS + mysql_user { 'test@short': + ensure => present, + } + mysql_grant { 'test@short/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@short', + privileges => 'ALL', + require => Mysql_user['test@short'], + } + mysql_user { 'test@long.hostname.com': + ensure => present, + } + mysql_grant { 'test@long.hostname.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@long.hostname.com', + privileges => 'ALL', + require => Mysql_user['test@long.hostname.com'], + } + mysql_user { 'test@192.168.5.6': + ensure => present, + } + mysql_grant { 'test@192.168.5.6/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@192.168.5.6', + privileges => 'ALL', + require => Mysql_user['test@192.168.5.6'], + } + mysql_user { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004': + ensure => present, + } + mysql_grant { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@2607:f0d0:1002:0051:0000:0000:0000:0004', + privileges => 'ALL', + require => Mysql_user['test@2607:f0d0:1002:0051:0000:0000:0000:0004'], + } + mysql_user { 'test@::1/128': + ensure => present, + } + mysql_grant { 'test@::1/128/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@::1/128', + privileges => 'ALL', + require => Mysql_user['test@::1/128'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'finds short hostname' do + shell("mysql -NBe \"SHOW GRANTS FOR test@short\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'short'/) + expect(r.stderr).to be_empty + end + end + it 'finds long hostname' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'long.hostname.com'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'long.hostname.com'/) + expect(r.stderr).to be_empty + end + end + it 'finds ipv4' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.6'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'192.168.5.6'/) + expect(r.stderr).to be_empty + end + end + it 'finds ipv6' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'/) + expect(r.stderr).to be_empty + end + end + it 'finds short ipv6' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'::1/128'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'::1\/128'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'complex test' do + it 'setup mysql::server' do + pp = <<-EOS + $dbSubnet = '10.10.10.%' + + mysql_database { 'foo': + ensure => present, + } + + exec { 'mysql-create-table': + command => '/usr/bin/mysql -NBe "CREATE TABLE foo.bar (name VARCHAR(20))"', + environment => "HOME=${::root_home}", + unless => '/usr/bin/mysql -NBe "SELECT 1 FROM foo.bar LIMIT 1;"', + require => Mysql_database['foo'], + } + + Mysql_grant { + ensure => present, + options => ['GRANT'], + privileges => ['ALL'], + table => '*.*', + require => [ Mysql_database['foo'], Exec['mysql-create-table'] ], + } + + mysql_user { "user1@${dbSubnet}": + ensure => present, + } + mysql_grant { "user1@${dbSubnet}/*.*": + user => "user1@${dbSubnet}", + require => Mysql_user["user1@${dbSubnet}"], + } + mysql_user { "user2@${dbSubnet}": + ensure => present, + } + mysql_grant { "user2@${dbSubnet}/foo.bar": + privileges => ['SELECT', 'INSERT', 'UPDATE'], + user => "user2@${dbSubnet}", + table => 'foo.bar', + require => Mysql_user["user2@${dbSubnet}"], + } + mysql_user { "user3@${dbSubnet}": + ensure => present, + } + mysql_grant { "user3@${dbSubnet}/foo.*": + privileges => ['SELECT', 'INSERT', 'UPDATE'], + user => "user3@${dbSubnet}", + table => 'foo.*', + require => Mysql_user["user3@${dbSubnet}"], + } + mysql_user { 'web@%': + ensure => present, + } + mysql_grant { 'web@%/*.*': + user => 'web@%', + require => Mysql_user['web@%'], + } + mysql_user { "web@${dbSubnet}": + ensure => present, + } + mysql_grant { "web@${dbSubnet}/*.*": + user => "web@${dbSubnet}", + require => Mysql_user["web@${dbSubnet}"], + } + mysql_user { "web@${fqdn}": + ensure => present, + } + mysql_grant { "web@${fqdn}/*.*": + user => "web@${fqdn}", + require => Mysql_user["web@${fqdn}"], + } + mysql_user { 'web@localhost': + ensure => present, + } + mysql_grant { 'web@localhost/*.*': + user => 'web@localhost', + require => Mysql_user['web@localhost'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + end + + describe 'lower case privileges' do + it 'create ALL privs' do + pp = <<-EOS + mysql_user { 'lowercase@localhost': + ensure => present, + } + mysql_grant { 'lowercase@localhost/*.*': + user => 'lowercase@localhost', + privileges => 'ALL', + table => '*.*', + require => Mysql_user['lowercase@localhost'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'create lowercase all privs' do + pp = <<-EOS + mysql_user { 'lowercase@localhost': + ensure => present, + } + mysql_grant { 'lowercase@localhost/*.*': + user => 'lowercase@localhost', + privileges => 'all', + table => '*.*', + require => Mysql_user['lowercase@localhost'], + } + EOS + + expect(apply_manifest(pp, :catch_failures => true).exit_code).to eq(0) + end + end + + describe 'adding procedure privileges' do + it 'should work without errors' do + pp = <<-EOS + if $::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemmajrelease, '16.00') > 0 { + exec { 'simpleproc-create': + command => 'mysql --user="root" --password="password" --database=mysql --delimiter="//" -NBe "CREATE PROCEDURE simpleproc (OUT param1 INT) BEGIN SELECT COUNT(*) INTO param1 FROM t; end//"', + path => '/usr/bin/', + before => Mysql_user['test2@tester'], + } + } + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test2@tester/PROCEDURE mysql.simpleproc': + ensure => 'present', + table => 'PROCEDURE mysql.simpleproc', + user => 'test2@tester', + privileges => ['EXECUTE'], + require => Mysql_user['test2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test2@tester\"") do |r| + expect(r.stdout).to match(/GRANT EXECUTE ON PROCEDURE `mysql`.`simpleproc` TO 'test2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'grants with skip-name-resolve specified' do + it 'setup mysql::server' do + pp = <<-EOS + class { 'mysql::server': + override_options => { + 'mysqld' => {'skip-name-resolve' => true} + }, + restart => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should apply' do + pp = <<-EOS + mysql_user { 'test@fqdn.com': + ensure => present, + } + mysql_grant { 'test@fqdn.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@fqdn.com', + privileges => 'ALL', + require => Mysql_user['test@fqdn.com'], + } + mysql_user { 'test@192.168.5.7': + ensure => present, + } + mysql_grant { 'test@192.168.5.7/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@192.168.5.7', + privileges => 'ALL', + require => Mysql_user['test@192.168.5.7'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should fail with fqdn' do + pre_run + if ! version_is_greater_than('5.7.0') + expect(shell("mysql -NBe \"SHOW GRANTS FOR test@fqdn.com\"", { :acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'test' on host 'fqdn.com'/) + end + end + it 'finds ipv4' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.7'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'192.168.5.7'/) + expect(r.stderr).to be_empty + end + end + + it 'should fail to execute while applying' do + pp = <<-EOS + mysql_user { 'test@fqdn.com': + ensure => present, + } + mysql_grant { 'test@fqdn.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@fqdn.com', + privileges => 'ALL', + require => Mysql_user['test@fqdn.com'], + } + EOS + + mysql_cmd = shell('which mysql').stdout.chomp + shell("mv #{mysql_cmd} #{mysql_cmd}.bak") + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Command mysql is missing/) + shell("mv #{mysql_cmd}.bak #{mysql_cmd}") + end + + it 'reset mysql::server config' do + pp = <<-EOS + class { 'mysql::server': + restart => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'adding privileges to specific table' do + # Using puppet_apply as a helper + it 'setup mysql server' do + pp = <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'creates grant on missing table will fail' do + pp = <<-EOS + mysql_user { 'test@localhost': + ensure => present, + } + mysql_grant { 'test@localhost/grant_spec_db.grant_spec_table': + user => 'test@localhost', + privileges => ['SELECT'], + table => 'grant_spec_db.grant_spec_table', + require => Mysql_user['test@localhost'], + } + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Table 'grant_spec_db\.grant_spec_table' doesn't exist/) + end + + it 'creates table' do + pp = <<-EOS + file { '/tmp/grant_spec_table.sql': + ensure => file, + content => 'CREATE TABLE grant_spec_table (id int);', + before => Mysql::Db['grant_spec_db'], + } + mysql::db { 'grant_spec_db': + user => 'root1', + password => 'password', + sql => '/tmp/grant_spec_table.sql', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should have the table' do + expect(shell("mysql -e 'show tables;' grant_spec_db|grep grant_spec_table").exit_code).to be_zero + end + end +end diff --git a/environments/prod/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb b/environments/prod/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb new file mode 100644 index 0000000..538b67c --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper_acceptance' + +# Different operating systems (and therefore different versions/forks +# of mysql) have varying levels of support for plugins and have +# different plugins available. Choose a plugin that works or don't try +# to test plugins if not available. +if fact('osfamily') =~ /RedHat/ + if fact('operatingsystemrelease') =~ /^5\./ + plugin = nil # Plugins not supported on mysql on RHEL 5 + elsif fact('operatingsystemrelease') =~ /^6\./ + plugin = 'example' + plugin_lib = 'ha_example.so' + elsif fact('operatingsystemrelease') =~ /^7\./ + plugin = 'pam' + plugin_lib = 'auth_pam.so' + end +elsif fact('osfamily') =~ /Debian/ + if fact('operatingsystem') =~ /Debian/ + if fact('operatingsystemrelease') =~ /^6\./ + # Only available plugin is innodb which is already loaded and not unload- or reload-able + plugin = nil + elsif fact('operatingsystemrelease') =~ /^7\./ + plugin = 'example' + plugin_lib = 'ha_example.so' + end + elsif fact('operatingsystem') =~ /Ubuntu/ + if fact('operatingsystemrelease') =~ /^10\.04/ + # Only available plugin is innodb which is already loaded and not unload- or reload-able + plugin = nil + elsif fact('operatingsystemrelease') =~ /^16\.04/ + # On Xenial running 5.7.12, the example plugin does not appear to be available. + plugin = 'validate_password' + plugin_lib = 'validate_password.so' + else + plugin = 'example' + plugin_lib = 'ha_example.so' + end + end +elsif fact('osfamily') =~ /Suse/ + plugin = nil # Plugin library path is broken on Suse http://lists.opensuse.org/opensuse-bugs/2013-08/msg01123.html +end + +describe 'mysql_plugin' do + if plugin # if plugins are supported + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'load plugin' do + it 'should work without errors' do + pp = <<-EOS + mysql_plugin { #{plugin}: + ensure => present, + soname => '#{plugin_lib}', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the plugin' do + shell("mysql -NBe \"select plugin_name from information_schema.plugins where plugin_name='#{plugin}'\"") do |r| + expect(r.stdout).to match(/^#{plugin}$/i) + expect(r.stderr).to be_empty + end + end + end + end + +end diff --git a/environments/prod/modules/mysql/spec/acceptance/types/mysql_user_spec.rb b/environments/prod/modules/mysql/spec/acceptance/types/mysql_user_spec.rb new file mode 100644 index 0000000..c91f65a --- /dev/null +++ b/environments/prod/modules/mysql/spec/acceptance/types/mysql_user_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper_acceptance' + +describe 'mysql_user' do + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + context 'using ashp@localhost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'has no SSL options' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^\s*$/) + expect(r.stderr).to be_empty + end + end + end + end + + context 'using ashp-dash@localhost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp-dash@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp-dash@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + end + end + + context 'using ashp@LocalHost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp@LocalHost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + end + end + context 'using resource should throw no errors' do + describe 'find users' do + it { + on default, puppet('resource mysql_user'), {:catch_failures => true} do |r| + expect(r.stdout).to_not match(/Error:/) + expect(r.stdout).to_not match(/must be properly quoted, invalid character:/) + end + } + end + end + context 'using user-w-ssl@localhost with SSL' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'user-w-ssl@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + tls_options => ['SSL'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'should show correct ssl_type' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| + expect(r.stdout).to match(/^ANY$/) + expect(r.stderr).to be_empty + end + end + end + end + context 'using user-w-x509@localhost with X509' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'user-w-x509@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + tls_options => ['X509'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'should show correct ssl_type' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| + expect(r.stdout).to match(/^X509$/) + expect(r.stderr).to be_empty + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/graceful_failures_spec.rb b/environments/prod/modules/mysql/spec/classes/graceful_failures_spec.rb new file mode 100644 index 0000000..9fb8117 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/graceful_failures_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'mysql::server' do + context "on an unsupported OS" do + # fetch any sets of facts to modify them + os, facts = on_supported_os.first + + let(:facts) { + facts.merge({ + :osfamily => 'UNSUPPORTED', + :operatingsystem => 'UNSUPPORTED', + }) + } + + it 'should gracefully fail' do + is_expected.to compile.and_raise_error(/Unsupported platform:/) + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mycnf_template_spec.rb b/environments/prod/modules/mysql/spec/classes/mycnf_template_spec.rb new file mode 100644 index 0000000..9c0c433 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mycnf_template_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'mysql::server' do + context 'my.cnf template' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'normal entry' do + let(:params) {{ :override_options => { 'mysqld' => { 'socket' => '/var/lib/mysql/mysql.sock' } } }} + it do + is_expected.to contain_file('mysql-config-file').with({ + :mode => '0644', + :selinux_ignore_defaults => true, + }).with_content(/socket = \/var\/lib\/mysql\/mysql.sock/) + end + end + + describe 'array entry' do + let(:params) {{ :override_options => { 'mysqld' => { 'replicate-do-db' => ['base1', 'base2'], } }}} + it do + is_expected.to contain_file('mysql-config-file').with_content( + /.*replicate-do-db = base1\nreplicate-do-db = base2.*/ + ) + end + end + + describe 'skip-name-resolve set to an empty string' do + let(:params) {{ :override_options => { 'mysqld' => { 'skip-name-resolve' => '' }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/^skip-name-resolve$/) } + end + + describe 'ssl set to true' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => true }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/ssl/) } + it { is_expected.to contain_file('mysql-config-file').without_content(/ssl = true/) } + end + + describe 'ssl set to false' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => false }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/ssl = false/) } + end + + # ssl-disable (and ssl) are special cased within mysql. + describe 'possibility of disabling ssl completely' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => true, 'ssl-disable' => true }}}} + it { is_expected.to contain_file('mysql-config-file').without_content(/ssl = true/) } + end + + describe 'a non ssl option set to true' do + let(:params) {{ :override_options => { 'mysqld' => { 'test' => true }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/^test$/) } + it { is_expected.to contain_file('mysql-config-file').without_content(/test = true/) } + end + + context 'with includedir' do + let(:params) {{ :includedir => '/etc/my.cnf.d' }} + it 'makes the directory' do + is_expected.to contain_file('/etc/my.cnf.d').with({ + :ensure => :directory, + :mode => '0755', + }) + end + + it { is_expected.to contain_file('mysql-config-file').with_content(/!includedir/) } + end + + context 'without includedir' do + let(:params) {{ :includedir => '' }} + it 'shouldnt contain the directory' do + is_expected.not_to contain_file('mysql-config-file').with({ + :ensure => :directory, + :mode => '0755', + }) + end + + it { is_expected.to contain_file('mysql-config-file').without_content(/!includedir/) } + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_bindings_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_bindings_spec.rb new file mode 100644 index 0000000..066e876 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_bindings_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'mysql::bindings' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:params) {{ + 'java_enable' => true, + 'perl_enable' => true, + 'php_enable' => true, + 'python_enable' => true, + 'ruby_enable' => true, + 'client_dev' => true, + 'daemon_dev' => true, + 'client_dev_package_name' => 'libmysqlclient-devel', + 'daemon_dev_package_name' => 'mysql-devel', + }} + + it { is_expected.to contain_package('mysql-connector-java') } + it { is_expected.to contain_package('perl_mysql') } + it { is_expected.to contain_package('python-mysqldb') } + it { is_expected.to contain_package('ruby_mysql') } + it { is_expected.to contain_package('mysql-client_dev') } + it { is_expected.to contain_package('mysql-daemon_dev') } + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_client_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_client_spec.rb new file mode 100644 index 0000000..fdbcc19 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_client_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'mysql::client' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'with defaults' do + it { is_expected.not_to contain_class('mysql::bindings') } + it { is_expected.to contain_package('mysql_client') } + end + + context 'with bindings enabled' do + let(:params) {{ :bindings_enable => true }} + + it { is_expected.to contain_class('mysql::bindings') } + it { is_expected.to contain_package('mysql_client') } + end + + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + + it { is_expected.to contain_package('mysql_client') } + end + + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + + it { is_expected.not_to contain_package('mysql_client') } + end + + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_server_account_security_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_server_account_security_spec.rb new file mode 100644 index 0000000..d45c46e --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_server_account_security_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'mysql::server::account_security' do + on_supported_os.each do |os, facts| + context "on #{os}" do + context "with fqdn==myhost.mydomain" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'myhost.mydomain', + :hostname => 'myhost', + }) + } + + [ 'root@myhost.mydomain', + 'root@127.0.0.1', + 'root@::1', + '@myhost.mydomain', + '@localhost', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + + # When the hostname doesn't match the fqdn we also remove these. + # We don't need to test the inverse as when they match they are + # covered by the above list. + [ 'root@myhost', '@myhost' ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + + it 'should remove Mysql_database[test]' do + is_expected.to contain_mysql_database('test').with_ensure('absent') + end + end + + context "with fqdn==localhost" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'localhost', + :hostname => 'localhost', + }) + } + + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + 'root@localhost.localdomain', + '@localhost.localdomain', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + end + + context "with fqdn==localhost.localdomain" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'localhost.localdomain', + :hostname => 'localhost', + }) + } + + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + 'root@localhost.localdomain', + '@localhost.localdomain', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_server_backup_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_server_backup_spec.rb new file mode 100644 index 0000000..96cfdf4 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_server_backup_spec.rb @@ -0,0 +1,407 @@ +require 'spec_helper' + +describe 'mysql::server::backup' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:default_params) { + { 'backupuser' => 'testuser', + 'backuppassword' => 'testpass', + 'maxallowedpacket' => '1M', + 'backupdir' => '/tmp', + 'backuprotate' => '25', + 'delete_before_dump' => true, + 'execpath' => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + 'maxallowedpacket' => '1M', + } + } + + context 'standard conditions' do + let(:params) { default_params } + + # Cannot use that_requires here, doesn't work on classes. + it { is_expected.to contain_mysql_user('testuser@localhost').with( + :require => 'Class[Mysql::Server::Root_password]') } + + it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( + :privileges => ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS'] + ).that_requires('Mysql_user[testuser@localhost]') } + + context 'with triggers included' do + let(:params) do + { :include_triggers => true }.merge(default_params) + end + it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( + :privileges => ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER'] + ).that_requires('Mysql_user[testuser@localhost]') } + end + + it { is_expected.to contain_cron('mysql-backup').with( + :command => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + )} + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it { is_expected.to contain_file('mysqlbackupdir').with( + :path => '/tmp', + :ensure => 'directory' + )} + + it 'should have compression by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /bzcat -zc/ + ) + end + + it 'should skip backing up events table by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="--ignore-table=mysql.event"/ + ) + end + + it 'should not mention triggers by default because file_per_database is false' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*triggers.*/ + ) + end + + it 'should not mention routines by default because file_per_database is false' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*routines.*/ + ) + end + + it 'should have 25 days of rotation' do + # MySQL counts from 0 + is_expected.to contain_file('mysqlbackup.sh').with_content(/.*ROTATE=24.*/) + end + + it 'should have a standard PATH' do + is_expected.to contain_file('mysqlbackup.sh').with_content(%r{PATH=/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin}) + end + end + + context 'custom ownership and mode for backupdir' do + let(:params) do + { :backupdirmode => '0750', + :backupdirowner => 'testuser', + :backupdirgroup => 'testgrp', + }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackupdir').with( + :path => '/tmp', + :ensure => 'directory', + :mode => '0750', + :owner => 'testuser', + :group => 'testgrp' + ) } + end + + context 'with compression disabled' do + let(:params) do + { :backupcompress => false }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it 'should be able to disable compression' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*bzcat -zc.*/ + ) + end + end + + context 'with mysql.events backedup' do + let(:params) do + { :ignore_events => false }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it 'should be able to backup events table' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="--events"/ + ) + end + end + + context 'with database list specified' do + let(:params) do + { :backupdatabases => ['mysql'] }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) + } + + it 'should have a backup file for each database' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /mysql | bzcat -zc \${DIR}\\\${PREFIX}mysql_`date'/ + ) + end + + it 'should skip backup triggers by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + + it 'should skip backing up routines by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + + context 'with include_triggers set to true' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => true + }) + end + + it 'should backup triggers when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"/ + ) + end + end + + context 'with include_triggers set to false' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => false + }) + end + + it 'should skip backing up triggers when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + end + + context 'with include_routines set to true' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_routines => true + }) + end + + it 'should backup routines when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"/ + ) + end + end + + context 'with include_routines set to false' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => true + }) + end + + it 'should skip backing up routines when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + end + end + + context 'with file per database' do + let(:params) do + default_params.merge({ :file_per_database => true }) + end + + it 'should loop through backup all databases' do + is_expected.to contain_file('mysqlbackup.sh').with_content(/.*SHOW DATABASES.*/) + end + + context 'with compression disabled' do + let(:params) do + default_params.merge({ :file_per_database => true, :backupcompress => false }) + end + + it 'should loop through backup all databases without compression' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /.*SHOW DATABASES.*/ + ) + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*bzcat -zc.*/ + ) + end + end + + it 'should skip backup triggers by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + + it 'should skip backing up routines by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + + context 'with include_triggers set to true' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => true + }) + end + + it 'should backup triggers when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"/ + ) + end + end + + context 'with include_triggers set to false' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => false + }) + end + + it 'should skip backing up triggers when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + end + + context 'with include_routines set to true' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_routines => true + }) + end + + it 'should backup routines when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"/ + ) + end + end + + context 'with include_routines set to false' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => true + }) + end + + it 'should skip backing up routines when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + end + end + + context 'with postscript' do + let(:params) do + default_params.merge({ :postscript => 'rsync -a /tmp backup01.local-lan:' }) + end + + it 'should be add postscript' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /rsync -a \/tmp backup01.local-lan:/ + ) + end + end + + context 'with postscripts' do + let(:params) do + default_params.merge({ :postscript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ]}) + end + + it 'should be add postscript' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + + context 'with the xtrabackup provider' do + let(:params) do + default_params.merge({:provider => 'xtrabackup'}) + end + + it 'should contain the wrapper script' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /^innobackupex\s+"\$@"/ + ) + end + + context 'with prescript defined' do + let(:params) do + default_params.merge({ + :provider => 'xtrabackup', + :prescript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ] + }) + end + + it 'should contain the prescript' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + + context 'with postscript defined' do + let(:params) do + default_params.merge({ + :provider => 'xtrabackup', + :postscript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ] + }) + end + + it 'should contain the prostscript' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_server_monitor_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_server_monitor_spec.rb new file mode 100644 index 0000000..27fab17 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_server_monitor_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +describe 'mysql::server::monitor' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let :pre_condition do + "include 'mysql::server'" + end + + let :default_params do + { + :mysql_monitor_username => 'monitoruser', + :mysql_monitor_password => 'monitorpass', + :mysql_monitor_hostname => 'monitorhost', + } + end + + let :params do + default_params + end + + it { is_expected.to contain_mysql_user('monitoruser@monitorhost')} + + it { is_expected.to contain_mysql_grant('monitoruser@monitorhost/*.*').with( + :ensure => 'present', + :user => 'monitoruser@monitorhost', + :table => '*.*', + :privileges => ["PROCESS", "SUPER"], + :require => 'Mysql_user[monitoruser@monitorhost]' + )} + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb new file mode 100644 index 0000000..4fef3d5 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'mysql::server::mysqltuner' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :staging_http_get => 'curl', + :root_home => '/root', + }) + } + + context 'ensure => present' do + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-v1.3.0').with({ + :source => 'https://github.com/major/MySQLTuner-perl/raw/v1.3.0/mysqltuner.pl', + }) + } + end + + context 'ensure => absent' do + let(:params) {{ :ensure => 'absent' }} + it { is_expected.to compile } + it { is_expected.to contain_file('/usr/local/bin/mysqltuner').with(:ensure => 'absent') } + end + + context 'custom version' do + let(:params) {{ :version => 'v1.2.0' }} + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-v1.2.0').with({ + :source => 'https://github.com/major/MySQLTuner-perl/raw/v1.2.0/mysqltuner.pl', + }) + } + end + + context 'custom source' do + let(:params) {{ :source => '/tmp/foo' }} + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-/tmp/foo').with({ + :source => '/tmp/foo', + }) + } + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/classes/mysql_server_spec.rb b/environments/prod/modules/mysql/spec/classes/mysql_server_spec.rb new file mode 100644 index 0000000..2a1ca62 --- /dev/null +++ b/environments/prod/modules/mysql/spec/classes/mysql_server_spec.rb @@ -0,0 +1,220 @@ +require 'spec_helper' + +describe 'mysql::server' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'with defaults' do + it { is_expected.to contain_class('mysql::server::install') } + it { is_expected.to contain_class('mysql::server::config') } + it { is_expected.to contain_class('mysql::server::service') } + it { is_expected.to contain_class('mysql::server::root_password') } + it { is_expected.to contain_class('mysql::server::providers') } + end + + context 'with remove_default_accounts set' do + let(:params) {{ :remove_default_accounts => true }} + it { is_expected.to contain_class('mysql::server::account_security') } + end + + context 'when not managing config file' do + let(:params) {{ :manage_config_file => false }} + it { is_expected.to compile.with_all_deps } + end + + context 'when not managing the service' do + let(:params) {{ :service_manage => false }} + it { is_expected.to compile.with_all_deps } + it { is_expected.not_to contain_service('mysqld') } + end + + context 'mysql::server::install' do + it 'contains the package by default' do + is_expected.to contain_package('mysql-server').with({ + :ensure => :present, + }) + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_package('mysql-server') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.not_to contain_package('mysql-server') } + end + context 'with datadir overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'datadir' => '/tmp' }} }} + it { is_expected.to contain_mysql_datadir('/tmp') } + end + end + + context 'mysql::server::service' do + context 'with defaults' do + it { is_expected.to contain_service('mysqld') } + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_service('mysqld').that_requires('Package[mysql-server]') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.to contain_service('mysqld') } + it { is_expected.not_to contain_service('mysqld').that_requires('Package[mysql-server]') } + end + context 'service_enabled set to false' do + let(:params) {{ :service_enabled => false }} + + it do + is_expected.to contain_service('mysqld').with({ + :ensure => :stopped + }) + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_package('mysql-server') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.not_to contain_package('mysql-server') } + end + context 'with datadir overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'datadir' => '/tmp' }} }} + it { is_expected.to contain_mysql_datadir('/tmp') } + end + end + context 'with log-error overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'log-error' => '/tmp/error.log' }} }} + it { is_expected.to contain_file('/tmp/error.log') } + end + end + + context 'mysql::server::root_password' do + describe 'when defaults' do + it { + is_expected.to contain_exec('remove install pass').with( + :command => 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /.mysql_secret) password \'\' && rm -f /.mysql_secret', + :onlyif => 'test -f /.mysql_secret', + :path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + ) + } + it { is_expected.not_to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when root_password set' do + let(:params) {{:root_password => 'SET' }} + it { is_expected.to contain_mysql_user('root@localhost') } + if Puppet.version.to_f >= 3.0 + it { is_expected.to contain_file('/root/.my.cnf').with(:show_diff => false).that_requires('Mysql_user[root@localhost]') } + else + it { is_expected.to contain_file('/root/.my.cnf').that_requires('Mysql_user[root@localhost]') } + end + end + describe 'when root_password set, create_root_user set to false' do + let(:params) {{ :root_password => 'SET', :create_root_user => false }} + it { is_expected.not_to contain_mysql_user('root@localhost') } + if Puppet.version.to_f >= 3.0 + it { is_expected.to contain_file('/root/.my.cnf').with(:show_diff => false) } + else + it { is_expected.to contain_file('/root/.my.cnf') } + end + end + describe 'when root_password set, create_root_my_cnf set to false' do + let(:params) {{ :root_password => 'SET', :create_root_my_cnf => false }} + it { is_expected.to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when root_password set, create_root_user and create_root_my_cnf set to false' do + let(:params) {{ :root_password => 'SET', :create_root_user => false, :create_root_my_cnf => false }} + it { is_expected.not_to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when install_secret_file set to /root/.mysql_secret' do + let(:params) {{ :install_secret_file => '/root/.mysql_secret' }} + it { + is_expected.to contain_exec('remove install pass').with( + :command => 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /root/.mysql_secret) password \'\' && rm -f /root/.mysql_secret', + :onlyif => 'test -f /root/.mysql_secret' + ) + } + end + end + + context 'mysql::server::providers' do + describe 'with users' do + let(:params) {{:users => { + 'foo@localhost' => { + 'max_connections_per_hour' => '1', + 'max_queries_per_hour' => '2', + 'max_updates_per_hour' => '3', + 'max_user_connections' => '4', + 'password_hash' => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' + }, + 'foo2@localhost' => {} + }}} + it { is_expected.to contain_mysql_user('foo@localhost').with( + :max_connections_per_hour => '1', + :max_queries_per_hour => '2', + :max_updates_per_hour => '3', + :max_user_connections => '4', + :password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' + )} + it { is_expected.to contain_mysql_user('foo2@localhost').with( + :max_connections_per_hour => nil, + :max_queries_per_hour => nil, + :max_updates_per_hour => nil, + :max_user_connections => nil, + :password_hash => nil + )} + end + + describe 'with grants' do + let(:params) {{:grants => { + 'foo@localhost/somedb.*' => { + 'user' => 'foo@localhost', + 'table' => 'somedb.*', + 'privileges' => ["SELECT", "UPDATE"], + 'options' => ["GRANT"], + }, + 'foo2@localhost/*.*' => { + 'user' => 'foo2@localhost', + 'table' => '*.*', + 'privileges' => ["SELECT"], + }, + }}} + it { is_expected.to contain_mysql_grant('foo@localhost/somedb.*').with( + :user => 'foo@localhost', + :table => 'somedb.*', + :privileges => ["SELECT", "UPDATE"], + :options => ["GRANT"] + )} + it { is_expected.to contain_mysql_grant('foo2@localhost/*.*').with( + :user => 'foo2@localhost', + :table => '*.*', + :privileges => ["SELECT"], + :options => nil + )} + end + + describe 'with databases' do + let(:params) {{:databases => { + 'somedb' => { + 'charset' => 'latin1', + 'collate' => 'latin1', + }, + 'somedb2' => {} + }}} + it { is_expected.to contain_mysql_database('somedb').with( + :charset => 'latin1', + :collate => 'latin1' + )} + it { is_expected.to contain_mysql_database('somedb2')} + end + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/defines/mysql_db_spec.rb b/environments/prod/modules/mysql/spec/defines/mysql_db_spec.rb new file mode 100644 index 0000000..0cd9cb2 --- /dev/null +++ b/environments/prod/modules/mysql/spec/defines/mysql_db_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe 'mysql::db', :type => :define do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:title) { 'test_db' } + + let(:params) { + { 'user' => 'testuser', + 'password' => 'testpass', + } + } + + it 'should report an error when ensure is not present or absent' do + params.merge!({'ensure' => 'invalid_val'}) + expect { catalogue }.to raise_error(Puppet::Error, + /invalid_val is not supported for ensure\. Allowed values are 'present' and 'absent'\./) + end + + it 'should not notify the import sql exec if no sql script was provided' do + is_expected.to contain_mysql_database('test_db').without_notify + end + + it 'should subscribe to database if sql script is given' do + params.merge!({'sql' => 'test_sql'}) + is_expected.to contain_exec('test_db-import').with_subscribe('Mysql_database[test_db]') + end + + it 'should only import sql script on creation if not enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => false}) + is_expected.to contain_exec('test_db-import').with_refreshonly(true) + end + + it 'should import sql script on creation if enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => true}) + is_expected.to contain_exec('test_db-import').with_refreshonly(false) + is_expected.to contain_exec('test_db-import').with_command('cat test_sql | mysql test_db') + end + + it 'should import sql script with custom command on creation if enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => true, 'import_cat_cmd' => 'zcat'}) + is_expected.to contain_exec('test_db-import').with_refreshonly(false) + is_expected.to contain_exec('test_db-import').with_command('zcat test_sql | mysql test_db') + end + + it 'should import sql scripts when more than one is specified' do + params.merge!({'sql' => ['test_sql', 'test_2_sql']}) + is_expected.to contain_exec('test_db-import').with_command('cat test_sql test_2_sql | mysql test_db') + end + + it 'should report an error if sql isn\'t a string or an array' do + params.merge!({'sql' => {'foo' => 'test_sql', 'bar' => 'test_2_sql'}}) + expect { catalogue }.to raise_error(Puppet::Error, + /\$sql must be either a string or an array\./) + end + + it 'should not create database and database user' do + params.merge!({'ensure' => 'absent', 'host' => 'localhost'}) + is_expected.to contain_mysql_database('test_db').with_ensure('absent') + is_expected.to contain_mysql_user('testuser@localhost').with_ensure('absent') + end + + it 'should create with an appropriate collate and charset' do + params.merge!({'charset' => 'utf8', 'collate' => 'utf8_danish_ci'}) + is_expected.to contain_mysql_database('test_db').with({ + 'charset' => 'utf8', + 'collate' => 'utf8_danish_ci', + }) + end + + it 'should use dbname parameter as database name instead of name' do + params.merge!({'dbname' => 'real_db'}) + is_expected.to contain_mysql_database('real_db') + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/spec.opts b/environments/prod/modules/mysql/spec/spec.opts new file mode 100644 index 0000000..91cd642 --- /dev/null +++ b/environments/prod/modules/mysql/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/environments/prod/modules/mysql/spec/spec_helper.rb b/environments/prod/modules/mysql/spec/spec_helper.rb new file mode 100644 index 0000000..22d5d68 --- /dev/null +++ b/environments/prod/modules/mysql/spec/spec_helper.rb @@ -0,0 +1,8 @@ +#This file is generated by ModuleSync, do not edit. +require 'puppetlabs_spec_helper/module_spec_helper' + +# put local configuration and setup into spec_helper_local +begin + require 'spec_helper_local' +rescue LoadError +end diff --git a/environments/prod/modules/mysql/spec/spec_helper_acceptance.rb b/environments/prod/modules/mysql/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..07e19bd --- /dev/null +++ b/environments/prod/modules/mysql/spec/spec_helper_acceptance.rb @@ -0,0 +1,66 @@ +require 'beaker-rspec' +require 'beaker/puppet_install_helper' + +run_puppet_install_helper + +UNSUPPORTED_PLATFORMS = [ 'Windows', 'Solaris', 'AIX' ] + +RSpec.configure do |c| + # Project root + proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + + # Readable test descriptions + c.formatter = :documentation + + # detect the situation where PUP-5016 is triggered and skip the idempotency tests in that case + # also note how fact('puppetversion') is not available because of PUP-4359 + if fact('osfamily') == 'Debian' && fact('operatingsystemmajrelease') == '8' && shell('puppet --version').stdout =~ /^4\.2/ + c.filter_run_excluding :skip_pup_5016 => true + end + + # Configure all nodes in nodeset + c.before :suite do + # Install module and dependencies + puppet_module_install(:source => proj_root, :module_name => 'mysql') + hosts.each do |host| + # Required for binding tests. + if fact('osfamily') == 'RedHat' + version = fact("operatingsystemmajrelease") + if fact('operatingsystemmajrelease') =~ /7/ || fact('operatingsystem') =~ /Fedora/ + shell("yum install -y bzip2") + end + end + + # Solaris 11 doesn't ship the SSL CA root for the forgeapi server + # therefore we need to use a different way to deploy the module to + # the host + if host['platform'] =~ /solaris-11/i + apply_manifest_on(host, 'package { "git": }') + # PE 3.x and 2015.2 require different locations to install modules + modulepath = host.puppet['modulepath'] + modulepath = modulepath.split(':').first if modulepath + + environmentpath = host.puppet['environmentpath'] + environmentpath = environmentpath.split(':').first if environmentpath + + destdir = modulepath || "#{environmentpath}/production/modules" + on host, "git clone https://github.com/puppetlabs/puppetlabs-stdlib #{destdir}/stdlib && cd #{destdir}/stdlib && git checkout 3.2.0" + on host, "git clone https://github.com/stahnma/puppet-module-epel.git #{destdir}/epel && cd #{destdir}/epel && git checkout 1.0.2" + else + on host, puppet('module','install','puppetlabs-stdlib','--version','3.2.0') + on host, puppet('module','install','stahnma/epel') + on host, puppet('module','install','puppet/staging') + end + end + end +end + +shared_examples "a idempotent resource" do + it 'should apply with no errors' do + apply_manifest(pp, :catch_failures => true) + end + + it 'should apply a second time without changes', :skip_pup_5016 do + apply_manifest(pp, :catch_changes => true) + end +end diff --git a/environments/prod/modules/mysql/spec/spec_helper_local.rb b/environments/prod/modules/mysql/spec/spec_helper_local.rb new file mode 100644 index 0000000..9a86ccd --- /dev/null +++ b/environments/prod/modules/mysql/spec/spec_helper_local.rb @@ -0,0 +1,3 @@ +require 'rspec-puppet-facts' +include RspecPuppetFacts + diff --git a/environments/prod/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb b/environments/prod/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb new file mode 100644 index 0000000..a2e9fdf --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysql_server_id" do + context "igalic's laptop" do + before :each do + Facter.fact(:macaddress).stubs(:value).returns('3c:97:0e:69:fb:e1') + end + it do + Facter.fact(:mysql_server_id).value.to_s.should == '4116385' + end + end + + context "node with lo only" do + before :each do + Facter.fact(:macaddress).stubs(:value).returns('00:00:00:00:00:00') + end + it do + Facter.fact(:mysql_server_id).value.to_s.should == '0' + end + end + end +end diff --git a/environments/prod/modules/mysql/spec/unit/facter/mysql_version_spec.rb b/environments/prod/modules/mysql/spec/unit/facter/mysql_version_spec.rb new file mode 100644 index 0000000..60379f6 --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/facter/mysql_version_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysql_version" do + context 'with value' do + before :each do + Facter::Util::Resolution.stubs(:exec).with('mysql --version').returns('mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1') + end + it { + expect(Facter.fact(:mysql_version).value).to eq('5.0.95') + } + end + + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/facter/mysqld_version_spec.rb b/environments/prod/modules/mysql/spec/unit/facter/mysqld_version_spec.rb new file mode 100644 index 0000000..f0b606f --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/facter/mysqld_version_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysqld_version" do + context 'with value' do + before :each do + Facter::Util::Resolution.stubs(:exec).with('mysqld -V 2>/dev/null').returns('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') + end + it { + expect(Facter.fact(:mysqld_version).value).to eq('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') + } + end + + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb new file mode 100644 index 0000000..18cb26b --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb @@ -0,0 +1,91 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:mysql_deepmerge) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe 'when calling mysql_deepmerge from puppet' do + it "should not compile when no arguments are passed" do + skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = '$x = mysql_deepmerge()' + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + + it "should not compile when 1 argument is passed" do + skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = "$my_hash={'one' => 1}\n$x = mysql_deepmerge($my_hash)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end + + describe 'when calling mysql_deepmerge on the scope instance' do + it 'should require all parameters are hashes' do + expect { new_hash = scope.function_mysql_deepmerge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/) + expect { new_hash = scope.function_mysql_deepmerge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) + end + + it 'should accept empty strings as puppet undef' do + expect { new_hash = scope.function_mysql_deepmerge([{}, ''])}.not_to raise_error + end + + it 'should be able to mysql_deepmerge two hashes' do + new_hash = scope.function_mysql_deepmerge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}]) + expect(new_hash['one']).to eq('1') + expect(new_hash['two']).to eq('2') + expect(new_hash['three']).to eq('2') + end + + it 'should mysql_deepmerge multiple hashes' do + hash = scope.function_mysql_deepmerge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}]) + expect(hash['one']).to eq('3') + end + + it 'should accept empty hashes' do + expect(scope.function_mysql_deepmerge([{},{},{}])).to eq({}) + end + + it 'should mysql_deepmerge subhashes' do + hash = scope.function_mysql_deepmerge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }]) + expect(hash['one']).to eq(1) + expect(hash['two']).to eq(2) + expect(hash['three']).to eq({ 'four' => 4 }) + end + + it 'should append to subhashes' do + hash = scope.function_mysql_deepmerge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }]) + expect(hash['one']).to eq({ 'two' => 2, 'three' => 3 }) + end + + it 'should append to subhashes 2' do + hash = scope.function_mysql_deepmerge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }]) + expect(hash['one']).to eq(1) + expect(hash['two']).to eq('dos') + expect(hash['three']).to eq({ 'four' => 4, 'five' => 5 }) + end + + it 'should append to subhashes 3' do + hash = scope.function_mysql_deepmerge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }]) + expect(hash['key1']).to eq({ 'a' => 1, 'b' => 99 }) + expect(hash['key2']).to eq({ 'c' => 3 }) + end + + it 'should equate keys mod dash and underscore' do + hash = scope.function_mysql_deepmerge([{ 'a-b-c' => 1 } , { 'a_b_c' => 10 }]) + expect(hash['a_b_c']).to eq(10) + expect(hash).not_to have_key('a-b-c') + end + + it 'should keep style of the last when keys are euqal mod dash and underscore' do + hash = scope.function_mysql_deepmerge([{ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 }} , { 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 } }]) + expect(hash['a_b_c']).to eq(10) + expect(hash).not_to have_key('a-b-c') + expect(hash['b-c-d']).to eq({ 'e-f-g' => 3, 'c_d_e' => 12 }) + expect(hash).not_to have_key('b_c_d') + end + end +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb new file mode 100644 index 0000000..2d5feea --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'the mysql_password function' do + before :all do + Puppet::Parser::Functions.autoloader.loadall + end + + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it 'should exist' do + expect(Puppet::Parser::Functions.function('mysql_password')).to eq('function_mysql_password') + end + + it 'should raise a ParseError if there is less than 1 arguments' do + expect { scope.function_mysql_password([]) }.to( raise_error(Puppet::ParseError)) + end + + it 'should raise a ParseError if there is more than 1 arguments' do + expect { scope.function_mysql_password(%w(foo bar)) }.to( raise_error(Puppet::ParseError)) + end + + it 'should convert password into a hash' do + result = scope.function_mysql_password(%w(password)) + expect(result).to(eq('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) + end + + it 'should convert an empty password into a empty string' do + result = scope.function_mysql_password([""]) + expect(result).to(eq('')) + end + + it 'should not convert a password that is already a hash' do + result = scope.function_mysql_password(['*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19']) + expect(result).to(eq('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb new file mode 100644 index 0000000..465e59d --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_database).provider(:mysql) do + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + + let(:raw_databases) do + <<-SQL_OUTPUT +information_schema +mydb +mysql +performance_schema +test + SQL_OUTPUT + end + + let(:parsed_databases) { %w(information_schema mydb mysql performance_schema test) } + + let(:resource) { Puppet::Type.type(:mysql_database).new( + { :ensure => :present, + :charset => 'latin1', + :collate => 'latin1_swedish_ci', + :name => 'new_database', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + Facter.stubs(:value).with(:root_home).returns('/root') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show databases']).returns('new_database') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "show variables like '%_database'", 'new_database']).returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") + end + + let(:instance) { provider.class.instances.first } + + describe 'self.instances' do + it 'returns an array of databases' do + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show databases']).returns(raw_databases) + raw_databases.each_line do |db| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "show variables like '%_database'", db.chomp]).returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") + end + databases = provider.class.instances.collect {|x| x.name } + expect(parsed_databases).to match_array(databases) + end + end + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'makes a database' do + provider.expects(:mysql).with([defaults_file, '-NBe', "create database if not exists `#{resource[:name]}` character set `#{resource[:charset]}` collate `#{resource[:collate]}`"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'removes a database if present' do + provider.expects(:mysql).with([defaults_file, '-NBe', "drop database if exists `#{resource[:name]}`"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if database exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.stubs(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'charset' do + it 'returns a charset' do + expect(instance.charset).to eq('latin1') + end + end + + describe 'charset=' do + it 'changes the charset' do + provider.expects(:mysql).with([defaults_file, '-NBe', "alter database `#{resource[:name]}` CHARACTER SET blah"]).returns('0') + + provider.charset=('blah') + end + end + + describe 'collate' do + it 'returns a collate' do + expect(instance.collate).to eq('latin1_swedish_ci') + end + end + + describe 'collate=' do + it 'changes the collate' do + provider.expects(:mysql).with([defaults_file, '-NBe', "alter database `#{resource[:name]}` COLLATE blah"]).returns('0') + + provider.collate=('blah') + end + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb new file mode 100644 index 0000000..340562f --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_plugin).provider(:mysql) do + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + + let(:resource) { Puppet::Type.type(:mysql_plugin).new( + { :ensure => :present, + :soname => 'auth_socket.so', + :name => 'auth_socket', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + Facter.stubs(:value).with(:root_home).returns('/root') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show plugins']).returns('auth_socket ACTIVE AUTHENTICATION auth_socket.so GPL') + end + + let(:instance) { provider.class.instances.first } + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'loads a plugin' do + provider.expects(:mysql).with([defaults_file, '-NBe', "install plugin #{resource[:name]} soname '#{resource[:soname]}'"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'unloads a plugin if present' do + provider.expects(:mysql).with([defaults_file, '-NBe', "uninstall plugin #{resource[:name]}"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if plugin exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.stubs(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'soname' do + it 'returns a soname' do + expect(instance.soname).to eq('auth_socket.so') + end + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb new file mode 100644 index 0000000..d993f42 --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb @@ -0,0 +1,345 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_user).provider(:mysql) do + + # Output of mysqld -V + mysql_version_string_hash = { + 'mysql-5.5' => + { + :version => '5.5.46', + :string => '/usr/sbin/mysqld Ver 5.5.46-log for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.6' => + { + :version => '5.6.27', + :string => '/usr/sbin/mysqld Ver 5.6.27 for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.7.1' => + { + :version => '5.7.1', + :string => '/usr/sbin/mysqld Ver 5.7.1 for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.7.6' => + { + :version => '5.7.8', + :string => '/usr/sbin/mysqld Ver 5.7.8-rc for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mariadb-10.0' => + { + :version => '10.0.21', + :string => '/usr/sbin/mysqld Ver 10.0.21-MariaDB for Linux on x86_64 (MariaDB Server)', + :mysql_type => 'mariadb', + }, + 'mariadb-10.0-deb8' => + { + :version => '10.0.23', + :string => '/usr/sbin/mysqld (mysqld 10.0.23-MariaDB-0+deb8u1)', + :mysql_type => 'mariadb', + }, + 'percona-5.5' => + { + :version => '5.5.39', + :string => 'mysqld Ver 5.5.39-36.0-55 for Linux on x86_64 (Percona XtraDB Cluster (GPL), Release rel36.0, Revision 824, WSREP version 25.11, wsrep_25.11.r4023)', + :mysql_type => 'percona', + }, + } + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + let(:system_database) { '--database=mysql' } + let(:newhash) { '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' } + + let(:raw_users) do + <<-SQL_OUTPUT +root@127.0.0.1 +root@::1 +@localhost +debian-sys-maint@localhost +root@localhost +usvn_user@localhost +@vagrant-ubuntu-raring-64 + SQL_OUTPUT + end + + let(:parsed_users) { %w(root@127.0.0.1 root@::1 @localhost debian-sys-maint@localhost root@localhost usvn_user@localhost @vagrant-ubuntu-raring-64) } + + let(:resource) { Puppet::Type.type(:mysql_user).new( + { :ensure => :present, + :password_hash => '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4', + :name => 'joe@localhost', + :max_user_connections => '10', + :max_connections_per_hour => '10', + :max_queries_per_hour => '10', + :max_updates_per_hour => '10', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + # Set up the stubs for an instances call. + Facter.stubs(:value).with(:root_home).returns('/root') + Facter.stubs(:value).with(:mysql_version).returns('5.6.24') + provider.class.instance_variable_set(:@mysqld_version_string, '5.6.24') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + Puppet::Util.stubs(:which).with('mysqld').returns('/usr/sbin/mysqld') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns('joe@localhost') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'"]).returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') + end + + let(:instance) { provider.class.instances.first } + + describe 'self.instances' do + it 'returns an array of users MySQL 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL >= 5.7.0 < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users mariadb 10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users percona 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + end + + describe 'mysql version and type detection' do + mysql_version_string_hash.each do |name,line| + version=line[:version] + string=line[:string] + mysql_type=line[:mysql_type] + it "detects type '#{mysql_type}' with version '#{version}'" do + provider.class.instance_variable_set(:@mysqld_version_string, string) + expect(provider.mysqld_version).to eq(version) + expect(provider.mysqld_type).to eq(mysql_type) + end + end + end + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'makes a user' do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "CREATE USER 'joe'@'localhost' IDENTIFIED BY PASSWORD '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'"]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10"]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'removes a user if present' do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "DROP USER 'joe'@'localhost'"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if user exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.mysqld_version' do + it 'uses the mysqld_version fact if unset' do + provider.class.instance_variable_set(:@mysqld_version_string, nil) + Facter.stubs(:value).with(:mysqld_version).returns('5.6.24') + expect(provider.mysqld_version).to eq '5.6.24' + end + it 'returns 5.7.6 for "mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))"' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))') + expect(provider.mysqld_version).to eq '5.7.6' + end + it 'returns 5.7.6 for "mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))"' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))') + expect(provider.mysqld_version).to eq '5.7.6' + end + it 'detects >= 5.7.6 for 5.7.7-log' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.7-log for Linux on x86_64 (MySQL Community Server (GPL))') + expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be >= 0 + end + it 'detects < 5.7.6 for 5.7.5-log' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.5-log for Linux on x86_64 (MySQL Community Server (GPL))') + expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be < 0 + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.expects(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'password_hash' do + it 'returns a hash' do + expect(instance.password_hash).to eq('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') + end + end + + describe 'password_hash=' do + it 'changes the hash mysql 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mysql 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mysql < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash MySQL >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "ALTER USER 'joe'@'localhost' IDENTIFIED WITH mysql_native_password AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mariadb-10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash percona-5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + end + + describe 'tls_options=' do + it 'adds SSL option grant in mysql 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "ALTER USER 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mariadb-10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + end + + ['max_user_connections', 'max_connections_per_hour', 'max_queries_per_hour', + 'max_updates_per_hour'].each do |property| + + describe property do + it "returns #{property}" do + expect(instance.send("#{property}".to_sym)).to eq('10') + end + end + + describe "#{property}=" do + it "changes #{property}" do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' WITH #{property.upcase} 42"]).returns('0') + provider.expects(property.to_sym).returns('42') + provider.send("#{property}=".to_sym, '42') + end + end + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb new file mode 100644 index 0000000..7897d81 --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb @@ -0,0 +1,29 @@ +require 'puppet' +require 'puppet/type/mysql_database' +describe Puppet::Type.type(:mysql_database) do + + before :each do + @user = Puppet::Type.type(:mysql_database).new(:name => 'test', :charset => 'utf8', :collate => 'utf8_blah_ci') + end + + it 'should accept a database name' do + expect(@user[:name]).to eq('test') + end + + it 'should accept a charset' do + @user[:charset] = 'latin1' + expect(@user[:charset]).to eq('latin1') + end + + it 'should accept a collate' do + @user[:collate] = 'latin1_swedish_ci' + expect(@user[:collate]).to eq('latin1_swedish_ci') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_database).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb new file mode 100644 index 0000000..9b33058 --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb @@ -0,0 +1,74 @@ +require 'puppet' +require 'puppet/type/mysql_grant' +describe Puppet::Type.type(:mysql_grant) do + + before :each do + @user = Puppet::Type.type(:mysql_grant).new(:name => 'foo@localhost/*.*', :privileges => ['ALL', 'PROXY'], :table => ['*.*','@'], :user => 'foo@localhost') + end + + it 'should accept a grant name' do + expect(@user[:name]).to eq('foo@localhost/*.*') + end + + it 'should accept ALL privileges' do + @user[:privileges] = 'ALL' + expect(@user[:privileges]).to eq(['ALL']) + end + + it 'should accept PROXY privilege' do + @user[:privileges] = 'PROXY' + expect(@user[:privileges]).to eq(['PROXY']) + end + + it 'should accept a table' do + @user[:table] = '*.*' + expect(@user[:table]).to eq('*.*') + end + + it 'should accept @ for table' do + @user[:table] = '@' + expect(@user[:table]).to eq('@') + end + + it 'should accept a user' do + @user[:user] = 'foo@localhost' + expect(@user[:user]).to eq('foo@localhost') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_grant).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + it 'should require the name to match the user and table' do + expect { + Puppet::Type.type(:mysql_grant).new(:name => 'foo', :privileges => ['ALL', 'PROXY'], :table => ['*.*','@'], :user => 'foo@localhost') + }.to raise_error /name must match user and table parameters/ + end + + describe 'it should munge privileges' do + + it 'to just ALL' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['ALL', 'PROXY'] ) + expect(@user[:privileges]).to eq(['ALL']) + end + + it 'to upcase and ordered' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['select', 'Insert'] ) + expect(@user[:privileges]).to eq(['INSERT', 'SELECT']) + end + + it 'ordered including column privileges' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['SELECT(Host,Address)', 'Insert'] ) + expect(@user[:privileges]).to eq(['INSERT', 'SELECT (Address, Host)']) + end + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb new file mode 100644 index 0000000..e94c518 --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb @@ -0,0 +1,24 @@ +require 'puppet' +require 'puppet/type/mysql_plugin' +describe Puppet::Type.type(:mysql_plugin) do + + before :each do + @plugin = Puppet::Type.type(:mysql_plugin).new(:name => 'test', :soname => 'test.so') + end + + it 'should accept a plugin name' do + expect(@plugin[:name]).to eq('test') + end + + it 'should accept a library name' do + @plugin[:soname] = 'test.so' + expect(@plugin[:soname]).to eq('test.so') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_plugin).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + +end diff --git a/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb new file mode 100644 index 0000000..3e3ae7d --- /dev/null +++ b/environments/prod/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb @@ -0,0 +1,136 @@ +require 'puppet' +require 'puppet/type/mysql_user' +describe Puppet::Type.type(:mysql_user) do + + context "On MySQL 5.x" do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("5.6.24") + end + it 'should fail with a long user name' do + expect { + Puppet::Type.type(:mysql_user).new({:name => '12345678901234567@localhost', :password_hash => 'pass'}) + }.to raise_error /MySQL usernames are limited to a maximum of 16 characters/ + end + end + + context "On MariaDB 10.0.0+" do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("10.0.19") + @user = Puppet::Type.type(:mysql_user).new(:name => '12345678901234567@localhost', :password_hash => 'pass') + end + it 'should succeed with a long user name on MariaDB' do + expect(@user[:name]).to eq('12345678901234567@localhost') + end + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_user).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + context 'using foo@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('foo@localhost') + end + + it 'should accept a password' do + @user[:password_hash] = 'foo' + expect(@user[:password_hash]).to eq('foo') + end + end + + context 'using foo@LocalHost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@LocalHost', :password_hash => 'pass') + end + + it 'should lowercase the user name' do + expect(@user[:name]).to eq('foo@localhost') + end + end + + context 'using foo@192.168.1.0/255.255.255.0' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@192.168.1.0/255.255.255.0', :password_hash => 'pass') + end + + it 'should create the user with the netmask' do + expect(@user[:name]).to eq('foo@192.168.1.0/255.255.255.0') + end + end + + context 'using allo_wed$char@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'allo_wed$char@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('allo_wed$char@localhost') + end + end + + context 'ensure the default \'debian-sys-main\'@localhost user can be parsed' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '\'debian-sys-maint\'@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('\'debian-sys-maint\'@localhost') + end + end + + context 'using a quoted 16 char username' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '"debian-sys-maint"@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('"debian-sys-maint"@localhost') + end + end + + context 'using a quoted username that is too long ' do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("5.6.24") + end + + it 'should fail with a size error' do + expect { + Puppet::Type.type(:mysql_user).new(:name => '"debian-sys-maint2"@localhost', :password_hash => 'pass') + }.to raise_error /MySQL usernames are limited to a maximum of 16 characters/ + end + end + + context 'using `speci!al#`@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '`speci!al#`@localhost', :password_hash => 'pass') + end + + it 'should accept a quoted user name with special chatracters' do + expect(@user[:name]).to eq('`speci!al#`@localhost') + end + end + + context 'using in-valid@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'in-valid@localhost', :password_hash => 'pass') + end + + it 'should accept a user name with special chatracters' do + expect(@user[:name]).to eq('in-valid@localhost') + end + end + + context 'using "misquoted@localhost' do + it 'should fail with a misquoted username is used' do + expect { + Puppet::Type.type(:mysql_user).new(:name => '"misquoted@localhost', :password_hash => 'pass') + }.to raise_error /Invalid database user "misquoted@localhost/ + end + end +end diff --git a/environments/prod/modules/mysql/templates/meb.cnf.erb b/environments/prod/modules/mysql/templates/meb.cnf.erb new file mode 100644 index 0000000..d157af9 --- /dev/null +++ b/environments/prod/modules/mysql/templates/meb.cnf.erb @@ -0,0 +1,18 @@ +### MANAGED BY PUPPET ### + +<% @options.sort.map do |k,v| -%> +<% if v.is_a?(Hash) -%> +[<%= k %>] +<% v.sort.map do |ki, vi| -%> +<% if vi == true or v == '' -%> +<%= ki %> +<% elsif vi.is_a?(Array) -%> +<% vi.each do |vii| -%> +<%= ki %> = <%= vii %> +<% end -%> +<% elsif ![nil, '', :undef].include?(vi) -%> +<%= ki %> = <%= vi %> +<% end -%> +<% end -%> +<% end %> +<% end -%> diff --git a/environments/prod/modules/mysql/templates/my.cnf.erb b/environments/prod/modules/mysql/templates/my.cnf.erb new file mode 100644 index 0000000..7d1f148 --- /dev/null +++ b/environments/prod/modules/mysql/templates/my.cnf.erb @@ -0,0 +1,24 @@ +### MANAGED BY PUPPET ### + +<% @options.sort.map do |k,v| -%> +<% if v.is_a?(Hash) -%> +[<%= k %>] +<% v.sort.map do |ki, vi| -%> +<% if ki == 'ssl-disable' or (ki =~ /^ssl/ and v['ssl-disable'] == true) -%> +<% next %> +<% elsif vi == true or vi == '' -%> +<%= ki %> +<% elsif vi.is_a?(Array) -%> +<% vi.each do |vii| -%> +<%= ki %> = <%= vii %> +<% end -%> +<% elsif ![nil, '', :undef].include?(vi) -%> +<%= ki %> = <%= vi %> +<% end -%> +<% end -%> +<% end %> +<% end -%> + +<% if @includedir and @includedir != '' %> +!includedir <%= @includedir %> +<% end %> diff --git a/environments/prod/modules/mysql/templates/my.cnf.pass.erb b/environments/prod/modules/mysql/templates/my.cnf.pass.erb new file mode 100644 index 0000000..b82cca3 --- /dev/null +++ b/environments/prod/modules/mysql/templates/my.cnf.pass.erb @@ -0,0 +1,11 @@ +### MANAGED BY PUPPET ### + +<% %w(mysql client mysqldump mysqladmin mysqlcheck).each do |section| %> +[<%= section -%>] +user=root +host=localhost +<% unless scope.lookupvar('mysql::server::root_password') == 'UNSET' -%> +password='<%= scope.lookupvar('mysql::server::root_password') %>' +<% end -%> +socket=<%= @options['client']['socket'] %> +<% end %> diff --git a/environments/prod/modules/mysql/templates/mysqlbackup.sh.erb b/environments/prod/modules/mysql/templates/mysqlbackup.sh.erb new file mode 100755 index 0000000..cd193c5 --- /dev/null +++ b/environments/prod/modules/mysql/templates/mysqlbackup.sh.erb @@ -0,0 +1,104 @@ +<%- if @kernel == 'Linux' -%> +#!/bin/bash +<%- else -%> +#!/bin/sh +<%- end -%> +# +# MySQL Backup Script +# Dumps mysql databases to a file for another backup tool to pick up. +# +# MySQL code: +# GRANT SELECT, RELOAD, LOCK TABLES ON *.* TO 'user'@'localhost' +# IDENTIFIED BY 'password'; +# FLUSH PRIVILEGES; +# +##### START CONFIG ################################################### + +USER=<%= @backupuser %> +PASS='<%= @backuppassword %>' +MAX_ALLOWED_PACKET=<%= @maxallowedpacket %> +DIR=<%= @backupdir %> +ROTATE=<%= [ Integer(@backuprotate) - 1, 0 ].max %> + +# Create temporary mysql cnf file. +TMPFILE=`mktemp /tmp/backup.XXXXXX` || exit 1 +echo -e "[client]\npassword=$PASS\nuser=$USER\nmax_allowed_packet=$MAX_ALLOWED_PACKET" > $TMPFILE + +# Ensure backup directory exist. +mkdir -p $DIR + +PREFIX=mysql_backup_ +<% if @ignore_events %> +ADDITIONAL_OPTIONS="--ignore-table=mysql.event" +<% else %> +ADDITIONAL_OPTIONS="--events" +<% end %> +<%# Only include routines or triggers if we're doing a file per database -%> +<%# backup. This happens if we named databases, or if we explicitly set -%> +<%# file per database mode -%> +<% if !@backupdatabases.empty? || @file_per_database -%> +<% if @include_triggers -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --triggers" +<% else -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-triggers" +<% end -%> +<% if @include_routines -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --routines" +<% else -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-routines" +<% end -%> +<% end -%> + +##### STOP CONFIG #################################################### +PATH=<%= @execpath %> + + + +<%- if @kernel == 'Linux' -%> +set -o pipefail +<%- end -%> + +cleanup() +{ + find "${DIR}/" -maxdepth 1 -type f -name "${PREFIX}*.sql*" -mtime +${ROTATE} -print0 | xargs -0 -r rm -f +} + +<% if @delete_before_dump -%> +cleanup + +<% end -%> +<% if @backupdatabases.empty? -%> +<% if @file_per_database -%> +mysql --defaults-extra-file=$TMPFILE -s -r -N -e 'SHOW DATABASES' | while read dbname +do + mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + ${dbname} <% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}${dbname}_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +done +<% else -%> +mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + --all-databases <% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +<% end -%> +<% else -%> +<% @backupdatabases.each do |db| -%> +mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + <%= db %><% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}<%= db %>_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +<% end -%> +<% end -%> + +<% unless @delete_before_dump -%> +if [ $? -eq 0 ] ; then + cleanup +fi +<% end -%> + +<% if @postscript -%> + <%- [@postscript].flatten.compact.each do |script|%> +<%= script %> + <%- end -%> +<% end -%> + +# Remove temporary file +rm -f $TMPFILE diff --git a/environments/prod/modules/mysql/templates/xtrabackup.sh.erb b/environments/prod/modules/mysql/templates/xtrabackup.sh.erb new file mode 100644 index 0000000..1449398 --- /dev/null +++ b/environments/prod/modules/mysql/templates/xtrabackup.sh.erb @@ -0,0 +1,21 @@ +<%- if @kernel == 'Linux' -%> +#!/bin/bash +<%- else -%> +#!/bin/sh +<%- end -%> +# +# A wrapper for Xtrabackup +# +<% if @prescript -%> + <%- [@prescript].flatten.compact.each do |script| %> +<%= script %> + <%- end -%> +<% end -%> + +innobackupex "$@" + +<% if @postscript -%> + <%- [@postscript].flatten.compact.each do |script| %> +<%= script %> + <%- end -%> +<% end -%> diff --git a/environments/prod/modules/stdlib/CHANGELOG.md b/environments/prod/modules/stdlib/CHANGELOG.md new file mode 100644 index 0000000..eeca835 --- /dev/null +++ b/environments/prod/modules/stdlib/CHANGELOG.md @@ -0,0 +1,775 @@ +## Supported Release 4.16.0 +### Summary + +This release sees a massive update to all unit tests to test UTF8 characters. There are also multiple cleanups in preparation for internationalization. Alongside this, improvements to ipv6 support, a new length function compatible with Puppet 4, and an update to path types. Also contains multiple bug fixes around functionality and tests. + +#### Features +- Addition of coverage in all unit tests for functions, data and resource types for UTF8 for i18n. +- All strings within the readme and functions that are split over two lines have been combined in preparation for i18n parser/decorator. +- Improvement on the ipv6 support for type - Improves regex to catch some valid (but lesser known) ipv6 strings, mostly those which are a mix of ipv6 strings and embedded ipv6 numbers. +- Adds a new parameter `encoding` to allow non UTF-8 files to specify a file encoding. This prevents receiving the error message "invalid byte sequence in UTF-8" when special characters that are not UTF-8 encoded appear in the input stream, such as the copyright symbol. +- Addition of the new length function. Returns the length of a given string, array or hash. To eventually replace the deprecated size() function as can handle the new type functionality introduced in Puppet 4. +- Permit double slash in absolute/Unix path types. + +#### Bugfixes +- Fix unsupported data type error with rspec-puppet master. +- Now allows test module metadata.json to be read by Puppet. +- Fix acceptance test failure "Hiera is not a class". +- Removal of unsupported platforms and future parser setting in acceptance tests. +- Regex for tuple checking has been loosened. +- Ensure_packages function - Now only tries to apply the resource if not defined. +- (MODULES-4528) Use versioncmp to check Puppet version for 4.10.x compat. +- Adds comments to warn for UTF8 incompatibility of the functions that may not be compatible with UTF8 with Ruby < 2.4.0. + +## Supported Release 4.15.0 +### Summary + +This release introduces multiple new functions, a new fact and the addition of Ubuntu Xenial support. Also includes a bugfix and documentation update. + +#### Features +- Addition of puppet_server fact to return agents server. +- Addition of a pry function. +- Addition of tests for ensure_resources. +- Addition of FQDN UUID generation function. +- Addition of Ubuntu Xenial to OS Support. + +####Bugfixes +- Ensure_packages now works with Ruby < 2.0. +- Updated the documentation of str2bool function. + +## Supported Release 4.14.0 +### Summary + +Adds several new features and updates, especially around refining the deprecation and validate_legacy functions. Also includes a Gemfile update around an issue with parallel_tests dependancy for different versions of Ruby. + +#### Features +- Deprecation function now uses puppet stacktrace if available. +- join_key_to_values function now handles array values. If values are arrays, multiple keys are added for each element. +- Updated Gemfile to deal with parallel_tests Ruby dependancy (MODULES-3983). +- Updated/Fixed ipv4 regex validator (MODULES-3980). +- Deprecation clarification added to README. + +#### Bugfixes +- README typo fixes. +- Use .dup to duplicate classes for modification (MODULES-3829). +- Fixes spec failures that were caused by a change in the tested error message in validate_legacy_spec. +- Broken link to validate_legacy docs fixed. +- Updates deprecation tests to include future parser. + +## Supported Release 4.13.1 +### Summary + +This bugfix release addresses the `undefined method 'optional_repeated_param'` error messages seen by users of puppet 3.7. + +It also improves the user experience around function deprecations by emitting one warning per function(-name) instead of only one deprecation overall. This allows users to identify all deprecated functions used in one agent run, with less back-and-forth. + +#### Bugfixes + +* Emit deprecations warnings for each function, instead of once per process. (MODULES-3961) +* Use a universally available API for the v4 deprecation stubs of `is_*` and `validate_*`. (MODULES-3962) +* Make `getvar()` compatible to ruby 1.8.7. (MODULES-3969) +* Add v4 deprecation stubs for the `is_` counterparts of the deprecated functions to emit the deprecations warnings in all cases. + + +## Supported Release 4.13.0 +### Summary + +This version of stdlib deprecates a whole host of functions, and provides stepping stones to move to Puppet 4 type validations. Be sure to check out the new `deprecation()` and `validate_legacy()` functions to migrate off the deprecated v3-style data validations. + +Many thanks to all community contributors: bob, Dmitry Ilyin, Dominic Cleal, Joris, Joseph Yaworski, Loic Antoine-Gombeaud, Maksym Melnychok, Michiel Brandenburg, Nate Potter, Romain Tartière, Stephen Benjamin, and Steve Moore, as well as anyone contributing in the code review process and by submitting issues. + +Special thanks to [Voxpupuli's](https://voxpupuli.org/) Igor Galić for donating the puppet-tea types to kickstart this part of stdlib. + + +#### Deprecations +* `validate_absolute_path`, `validate_array`, `validate_bool`, `validate_hash`, `validate_integer`, `validate_ip_address`, `validate_ipv4_address`, `validate_ipv6_address`, `validate_numeric`, `validate_re`, `validate_slength`, `validate_string`, and their `is_` counter parts are now deprecated on Puppet 4. See the `validate_legacy()` description in the README for help on migrating away from those functions. +* The `dig` function is provided by core puppet since 4.5.0 with slightly different calling convention. The stdlib version can still be accessed as `dig44` for now. + + +#### Features +* Add Puppet 4 data types for Unix, and Windows paths, and URLs. +* Add `deprecation` function to warn users of functionality that will be removed soon. +* Add `validate_legacy` function to help with migrating to Puppet 4 data types. + +* Add `any2bool` function, a combination of of `string2bool` and `num2bool`. +* Add `delete_regex` function to delete array elements matching a regular expression. +* Add `puppet_environmentpath` fact to expose the `environmentpath` setting. +* Add `regexpescape` function to safely insert arbitrary strings into regular expressions. +* Add `shell_escape`, `shell_join`, and `shell_split` functions for safer working with shell scripts.. + +* The `delete` function now also accepts regular expressions as search term. +* The `loadyaml` function now accepts a default value, which is returned when there is an error loading the file. + +#### Bugfixes +* Fix `file_line.match_for_absence` implementation and description to actually work. (MODULES-3590) +* Fix `getparam` so that it can now also return `false`. (MODULES-3933) +* Fix the fixture setup for testing and adjust `load_module_metadata` and `loadjson` tests. +* Fix `defined_with_params` to handle `undef` correctly on all puppet versions. (PUP-6422, MODULES-3543) +* Fix `file_line.path` validation to use puppet's built in `absolute_path?` matcher. + +#### Minor Improvements +* README changes: improved descriptions of `deep_merge`, `delete`, `ensure_packages`, `file_line.after`, `range`, and `validate_numeric`. +* The `getvar` function now returns nil in all situations where the variable is not found. +* Update the `dig44` function with better `undef`, `nil`, and `false` handling. +* Better wording on `str2bool` argument validation error message. + + +### Known issues +* The `validate_legacy` function relies on internal APIs from Puppet 4.4.0 (PE 2016.1) onwards, and doesn't work on earlier versions. +* Puppet 4.5.0 (PE 2016.2) has a number of improvements around data types - especially error handling - that make working with them much nicer. + +## Supported Release 4.12.0 +###Summary + +This release provides several new functions, bugfixes, modulesync changes, and some documentation updates. + +####Features +- Adds `clamp`. This function keeps values within a specified range. +- Adds `validate_x509_rsa_key_pair`. This function validates an x509 RSA certificate and key pair. +- Adds `dig`. This function performs a deep lookup in nested hashes or arrays. +- Extends the `base64` support to fit `rfc2045` and `rfc4648`. +- Adds `is_ipv6_address` and `is_ipv4_address`. These functions validate the specified ipv4 or ipv6 addresses. +- Adds `enclose_ipv6`. This function encloses IPv6 addresses in square brackets. +- Adds `ensure_resources`. This function takes a list of resources and creates them if they do not exist. +- Extends `suffix` to support applying a suffix to keys in a hash. +- Apply modulesync changes. +- Add validate_email_address function. + +####Bugfixes +- Fixes `fqdn_rand_string` tests, since Puppet 4.4.0 and later have a higher `fqdn_rand` ceiling. +- (MODULES-3152) Adds a check to `package_provider` to prevent failures if Gem is not installed. +- Fixes to README.md. +- Fixes catch StandardError rather than the gratuitous Exception +- Fixes file_line attribute validation. +- Fixes concat with Hash arguments. + +## Supported Release 4.11.0 +###Summary + +Provides a validate_absolute_paths and Debian 8 support. There is a fix to the is_package_provider fact and a test improvement. + +####Features +- Adds new parser called is_absolute_path +- Supports Debian 8 + +####Bugfixes +- Allow package_provider fact to resolve on PE 3.x + +####Improvements +- ensures that the test passes independently of changes to rubygems for ensure_resource + +##2015-12-15 - Supported Release 4.10.0 +###Summary + +Includes the addition of several new functions and considerable improvements to the existing functions, tests and documentation. Includes some bug fixes which includes compatibility, test and fact issues. + +####Features +- Adds service_provider fact +- Adds is_a() function +- Adds package_provider fact +- Adds validate_ip_address function +- Adds seeded_rand function + +####Bugfixes +- Fix backwards compatibility from an improvement to the parseyaml function +- Renaming of load_module_metadata test to include _spec.rb +- Fix root_home fact on AIX 5.x, now '-c' rather than '-C' +- Fixed Gemfile to work with ruby 1.8.7 + +####Improvements +- (MODULES-2462) Improvement of parseyaml function +- Improvement of str2bool function +- Improvement to readme +- Improvement of intersection function +- Improvement of validate_re function +- Improved speed on Facter resolution of service_provider +- empty function now handles numeric values +- Package_provider now prevents deprecation warning about the allow_virtual parameter +- load_module_metadata now succeeds on empty file +- Check added to ensure puppetversion value is not nil +- Improvement to bool2str to return a string of choice using boolean +- Improvement to naming convention in validate_ipv4_address function + +## Supported Release 4.9.1 +###Summary + +Small release for support of newer PE versions. This increments the version of PE in the metadata.json file. + +##2015-09-08 - Supported Release 4.9.0 +###Summary + +This release adds new features including the new functions dos2unix, unix2dos, try_get_value, convert_base as well as other features and improvements. + +####Features +- (MODULES-2370) allow `match` parameter to influence `ensure => absent` behavior +- (MODULES-2410) Add new functions dos2unix and unix2dos +- (MODULE-2456) Modify union to accept more than two arrays +- Adds a convert_base function, which can convert numbers between bases +- Add a new function "try_get_value" + +####Bugfixes +- n/a + +####Improvements +- (MODULES-2478) Support root_home fact on AIX through "lsuser" command +- Acceptance test improvements +- Unit test improvements +- Readme improvements + +## 2015-08-10 - Supported Release 4.8.0 +### Summary +This release adds a function for reading metadata.json from any module, and expands file\_line's abilities. + +#### Features +- New parameter `replace` on `file_line` +- New function `load_module_metadata()` to load metadata.json and return the content as a hash. +- Added hash support to `size()` + +#### Bugfixes +- Fix various docs typos +- Fix `file_line` resource on puppet < 3.3 + +##2015-06-22 - Supported Release 4.7.0 +###Summary + +Adds Solaris 12 support along with improved Puppet 4 support. There are significant test improvements, and some minor fixes. + +####Features +- Add support for Solaris 12 + +####Bugfixes +- Fix for AIO Puppet 4 +- Fix time for ruby 1.8.7 +- Specify rspec-puppet version +- range() fix for typeerror and missing functionality +- Fix pw_hash() on JRuby < 1.7.17 +- fqdn_rand_string: fix argument error message +- catch and rescue from looking up non-existent facts +- Use puppet_install_helper, for Puppet 4 + +####Improvements +- Enforce support for Puppet 4 testing +- fqdn_rotate/fqdn_rand_string acceptance tests and implementation +- Simplify mac address regex +- validate_integer, validate_numeric: explicitely reject hashes in arrays +- Readme edits +- Remove all the pops stuff for rspec-puppet +- Sync via modulesync +- Add validate_slength optional 3rd arg +- Move tests directory to examples directory + +##2015-04-14 - Supported Release 4.6.0 +###Summary + +Adds functions and function argument abilities, and improves compatibility with the new puppet parser + +####Features +- MODULES-444: `concat()` can now take more than two arrays +- `basename()` added to have Ruby File.basename functionality +- `delete()` can now take an array of items to remove +- `prefix()` can now take a hash +- `upcase()` can now take a hash or array of upcaseable things +- `validate_absolute_path()` can now take an array +- `validate_cmd()` can now use % in the command to embed the validation file argument in the string +- MODULES-1473: deprecate `type()` function in favor of `type3x()` +- MODULES-1473: Add `type_of()` to give better type information on future parser +- Deprecate `private()` for `assert_private()` due to future parser +- Adds `ceiling()` to take the ceiling of a number +- Adds `fqdn_rand_string()` to generate random string based on fqdn +- Adds `pw_hash()` to generate password hashes +- Adds `validate_integer()` +- Adds `validate_numeric()` (like `validate_integer()` but also accepts floats) + +####Bugfixes +- Fix seeding of `fqdn_rotate()` +- `ensure_resource()` is more verbose on debug mode +- Stricter argument checking for `dirname()` +- Fix `is_domain_name()` to better match RFC +- Fix `uriescape()` when called with array +- Fix `file_line` resource when using the `after` attribute with `match` + +##2015-01-14 - Supported Release 4.5.1 +###Summary + +This release changes the temporary facter_dot_d cache locations outside of the /tmp directory due to a possible security vunerability. CVE-2015-1029 + +####Bugfixes +- Facter_dot_d cache will now be stored in puppet libdir instead of tmp + +##2014-12-15 - Supported Release 4.5.0 +###Summary + +This release improves functionality of the member function and adds improved future parser support. + +####Features +- MODULES-1329: Update member() to allow the variable to be an array. +- Sync .travis.yml, Gemfile, Rakefile, and CONTRIBUTING.md via modulesync + +####Bugfixes +- Fix range() to work with numeric ranges with the future parser +- Accurately express SLES support in metadata.json (was missing 10SP4 and 12) +- Don't require `line` to match the `match` parameter + +##2014-11-10 - Supported Release 4.4.0 +###Summary +This release has an overhauled readme, new private manifest function, and fixes many future parser bugs. + +####Features +- All new shiny README +- New `private()` function for making private manifests (yay!) + +####Bugfixes +- Code reuse in `bool2num()` and `zip()` +- Fix many functions to handle `generate()` no longer returning a string on new puppets +- `concat()` no longer modifies the first argument (whoops) +- strict variable support for `getvar()`, `member()`, `values_at`, and `has_interface_with()` +- `to_bytes()` handles PB and EB now +- Fix `tempfile` ruby requirement for `validate_augeas()` and `validate_cmd()` +- Fix `validate_cmd()` for windows +- Correct `validate_string()` docs to reflect non-handling of `undef` +- Fix `file_line` matching on older rubies + + +##2014-07-15 - Supported Release 4.3.2 +###Summary + +This release merely updates metadata.json so the module can be uninstalled and +upgraded via the puppet module command. + +##2014-07-14 - Supported Release 4.3.1 +### Summary +This supported release updates the metadata.json to work around upgrade behavior of the PMT. + +#### Bugfixes +- Synchronize metadata.json with PMT-generated metadata to pass checksums + +##2014-06-27 - Supported Release 4.3.0 +### Summary +This release is the first supported release of the stdlib 4 series. It remains +backwards-compatible with the stdlib 3 series. It adds two new functions, one bugfix, and many testing updates. + +#### Features +- New `bool2str()` function +- New `camelcase()` function + +#### Bugfixes +- Fix `has_interface_with()` when interfaces fact is nil + +##2014-06-04 - Release 4.2.2 +### Summary + +This release adds PE3.3 support in the metadata and fixes a few tests. + +## 2014-05-08 - Release - 4.2.1 +### Summary +This release moves a stray symlink that can cause problems. + +## 2014-05-08 - Release - 4.2.0 +### Summary +This release adds many new functions and fixes, and continues to be backwards compatible with stdlib 3.x + +#### Features +- New `base64()` function +- New `deep_merge()` function +- New `delete_undef_values()` function +- New `delete_values()` function +- New `difference()` function +- New `intersection()` function +- New `is_bool()` function +- New `pick_default()` function +- New `union()` function +- New `validate_ipv4_address` function +- New `validate_ipv6_address` function +- Update `ensure_packages()` to take an option hash as a second parameter. +- Update `range()` to take an optional third argument for range step +- Update `validate_slength()` to take an optional third argument for minimum length +- Update `file_line` resource to take `after` and `multiple` attributes + +#### Bugfixes +- Correct `is_string`, `is_domain_name`, `is_array`, `is_float`, and `is_function_available` for parsing odd types such as bools and hashes. +- Allow facts.d facts to contain `=` in the value +- Fix `root_home` fact on darwin systems +- Fix `concat()` to work with a second non-array argument +- Fix `floor()` to work with integer strings +- Fix `is_integer()` to return true if passed integer strings +- Fix `is_numeric()` to return true if passed integer strings +- Fix `merge()` to work with empty strings +- Fix `pick()` to raise the correct error type +- Fix `uriescape()` to use the default URI.escape list +- Add/update unit & acceptance tests. + + +##2014-03-04 - Supported Release - 3.2.1 +###Summary +This is a supported release + +####Bugfixes +- Fixed `is_integer`/`is_float`/`is_numeric` for checking the value of arithmatic expressions. + +####Known bugs +* No known bugs + +--- + +##### 2013-05-06 - Jeff McCune - 4.1.0 + + * (#20582) Restore facter\_dot\_d to stdlib for PE users (3b887c8) + * (maint) Update Gemfile with GEM\_FACTER\_VERSION (f44d535) + +##### 2013-05-06 - Alex Cline - 4.1.0 + + * Terser method of string to array conversion courtesy of ethooz. (d38bce0) + +##### 2013-05-06 - Alex Cline 4.1.0 + + * Refactor ensure\_resource expectations (b33cc24) + +##### 2013-05-06 - Alex Cline 4.1.0 + + * Changed str-to-array conversion and removed abbreviation. (de253db) + +##### 2013-05-03 - Alex Cline 4.1.0 + + * (#20548) Allow an array of resource titles to be passed into the ensure\_resource function (e08734a) + +##### 2013-05-02 - Raphaël Pinson - 4.1.0 + + * Add a dirname function (2ba9e47) + +##### 2013-04-29 - Mark Smith-Guerrero - 4.1.0 + + * (maint) Fix a small typo in hash() description (928036a) + +##### 2013-04-12 - Jeff McCune - 4.0.2 + + * Update user information in gemspec to make the intent of the Gem clear. + +##### 2013-04-11 - Jeff McCune - 4.0.1 + + * Fix README function documentation (ab3e30c) + +##### 2013-04-11 - Jeff McCune - 4.0.0 + + * stdlib 4.0 drops support with Puppet 2.7 + * stdlib 4.0 preserves support with Puppet 3 + +##### 2013-04-11 - Jeff McCune - 4.0.0 + + * Add ability to use puppet from git via bundler (9c5805f) + +##### 2013-04-10 - Jeff McCune - 4.0.0 + + * (maint) Make stdlib usable as a Ruby GEM (e81a45e) + +##### 2013-04-10 - Erik Dalén - 4.0.0 + + * Add a count function (f28550e) + +##### 2013-03-31 - Amos Shapira - 4.0.0 + + * (#19998) Implement any2array (7a2fb80) + +##### 2013-03-29 - Steve Huff - 4.0.0 + + * (19864) num2bool match fix (8d217f0) + +##### 2013-03-20 - Erik Dalén - 4.0.0 + + * Allow comparisons of Numeric and number as String (ff5dd5d) + +##### 2013-03-26 - Richard Soderberg - 4.0.0 + + * add suffix function to accompany the prefix function (88a93ac) + +##### 2013-03-19 - Kristof Willaert - 4.0.0 + + * Add floor function implementation and unit tests (0527341) + +##### 2012-04-03 - Eric Shamow - 4.0.0 + + * (#13610) Add is\_function\_available to stdlib (961dcab) + +##### 2012-12-17 - Justin Lambert - 4.0.0 + + * str2bool should return a boolean if called with a boolean (5d5a4d4) + +##### 2012-10-23 - Uwe Stuehler - 4.0.0 + + * Fix number of arguments check in flatten() (e80207b) + +##### 2013-03-11 - Jeff McCune - 4.0.0 + + * Add contributing document (96e19d0) + +##### 2013-03-04 - Raphaël Pinson - 4.0.0 + + * Add missing documentation for validate\_augeas and validate\_cmd to README.markdown (a1510a1) + +##### 2013-02-14 - Joshua Hoblitt - 4.0.0 + + * (#19272) Add has\_element() function (95cf3fe) + +##### 2013-02-07 - Raphaël Pinson - 4.0.0 + + * validate\_cmd(): Use Puppet::Util::Execution.execute when available (69248df) + +##### 2012-12-06 - Raphaël Pinson - 4.0.0 + + * Add validate\_augeas function (3a97c23) + +##### 2012-12-06 - Raphaël Pinson - 4.0.0 + + * Add validate\_cmd function (6902cc5) + +##### 2013-01-14 - David Schmitt - 4.0.0 + + * Add geppetto project definition (b3fc0a3) + +##### 2013-01-02 - Jaka Hudoklin - 4.0.0 + + * Add getparam function to get defined resource parameters (20e0e07) + +##### 2013-01-05 - Jeff McCune - 4.0.0 + + * (maint) Add Travis CI Support (d082046) + +##### 2012-12-04 - Jeff McCune - 4.0.0 + + * Clarify that stdlib 3 supports Puppet 3 (3a6085f) + +##### 2012-11-30 - Erik Dalén - 4.0.0 + + * maint: style guideline fixes (7742e5f) + +##### 2012-11-09 - James Fryman - 4.0.0 + + * puppet-lint cleanup (88acc52) + +##### 2012-11-06 - Joe Julian - 4.0.0 + + * Add function, uriescape, to URI.escape strings. Redmine #17459 (fd52b8d) + +##### 2012-09-18 - Chad Metcalf - 3.2.0 + + * Add an ensure\_packages function. (8a8c09e) + +##### 2012-11-23 - Erik Dalén - 3.2.0 + + * (#17797) min() and max() functions (9954133) + +##### 2012-05-23 - Peter Meier - 3.2.0 + + * (#14670) autorequire a file\_line resource's path (dfcee63) + +##### 2012-11-19 - Joshua Harlan Lifton - 3.2.0 + + * Add join\_keys\_to\_values function (ee0f2b3) + +##### 2012-11-17 - Joshua Harlan Lifton - 3.2.0 + + * Extend delete function for strings and hashes (7322e4d) + +##### 2012-08-03 - Gary Larizza - 3.2.0 + + * Add the pick() function (ba6dd13) + +##### 2012-03-20 - Wil Cooley - 3.2.0 + + * (#13974) Add predicate functions for interface facts (f819417) + +##### 2012-11-06 - Joe Julian - 3.2.0 + + * Add function, uriescape, to URI.escape strings. Redmine #17459 (70f4a0e) + +##### 2012-10-25 - Jeff McCune - 3.1.1 + + * (maint) Fix spec failures resulting from Facter API changes (97f836f) + +##### 2012-10-23 - Matthaus Owens - 3.1.0 + + * Add PE facts to stdlib (cdf3b05) + +##### 2012-08-16 - Jeff McCune - 3.0.1 + + * Fix accidental removal of facts\_dot\_d.rb in 3.0.0 release + +##### 2012-08-16 - Jeff McCune - 3.0.0 + + * stdlib 3.0 drops support with Puppet 2.6 + * stdlib 3.0 preserves support with Puppet 2.7 + +##### 2012-08-07 - Dan Bode - 3.0.0 + + * Add function ensure\_resource and defined\_with\_params (ba789de) + +##### 2012-07-10 - Hailee Kenney - 3.0.0 + + * (#2157) Remove facter\_dot\_d for compatibility with external facts (f92574f) + +##### 2012-04-10 - Chris Price - 3.0.0 + + * (#13693) moving logic from local spec\_helper to puppetlabs\_spec\_helper (85f96df) + +##### 2012-10-25 - Jeff McCune - 2.5.1 + + * (maint) Fix spec failures resulting from Facter API changes (97f836f) + +##### 2012-10-23 - Matthaus Owens - 2.5.0 + + * Add PE facts to stdlib (cdf3b05) + +##### 2012-08-15 - Dan Bode - 2.5.0 + + * Explicitly load functions used by ensure\_resource (9fc3063) + +##### 2012-08-13 - Dan Bode - 2.5.0 + + * Add better docs about duplicate resource failures (97d327a) + +##### 2012-08-13 - Dan Bode - 2.5.0 + + * Handle undef for parameter argument (4f8b133) + +##### 2012-08-07 - Dan Bode - 2.5.0 + + * Add function ensure\_resource and defined\_with\_params (a0cb8cd) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * Disable tests that fail on 2.6.x due to #15912 (c81496e) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * (Maint) Fix mis-use of rvalue functions as statements (4492913) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * Add .rspec file to repo root (88789e8) + +##### 2012-06-07 - Chris Price - 2.4.0 + + * Add support for a 'match' parameter to file\_line (a06c0d8) + +##### 2012-08-07 - Erik Dalén - 2.4.0 + + * (#15872) Add to\_bytes function (247b69c) + +##### 2012-07-19 - Jeff McCune - 2.4.0 + + * (Maint) use PuppetlabsSpec::PuppetInternals.scope (master) (deafe88) + +##### 2012-07-10 - Hailee Kenney - 2.4.0 + + * (#2157) Make facts\_dot\_d compatible with external facts (5fb0ddc) + +##### 2012-03-16 - Steve Traylen - 2.4.0 + + * (#13205) Rotate array/string randomley based on fqdn, fqdn\_rotate() (fef247b) + +##### 2012-05-22 - Peter Meier - 2.3.3 + + * fix regression in #11017 properly (f0a62c7) + +##### 2012-05-10 - Jeff McCune - 2.3.3 + + * Fix spec tests using the new spec\_helper (7d34333) + +##### 2012-05-10 - Puppet Labs - 2.3.2 + + * Make file\_line default to ensure => present (1373e70) + * Memoize file\_line spec instance variables (20aacc5) + * Fix spec tests using the new spec\_helper (1ebfa5d) + * (#13595) initialize\_everything\_for\_tests couples modules Puppet ver (3222f35) + * (#13439) Fix MRI 1.9 issue with spec\_helper (15c5fd1) + * (#13439) Fix test failures with Puppet 2.6.x (665610b) + * (#13439) refactor spec helper for compatibility with both puppet 2.7 and master (82194ca) + * (#13494) Specify the behavior of zero padded strings (61891bb) + +##### 2012-03-29 Puppet Labs - 2.1.3 + +* (#11607) Add Rakefile to enable spec testing +* (#12377) Avoid infinite loop when retrying require json + +##### 2012-03-13 Puppet Labs - 2.3.1 + +* (#13091) Fix LoadError bug with puppet apply and puppet\_vardir fact + +##### 2012-03-12 Puppet Labs - 2.3.0 + +* Add a large number of new Puppet functions +* Backwards compatibility preserved with 2.2.x + +##### 2011-12-30 Puppet Labs - 2.2.1 + +* Documentation only release for the Forge + +##### 2011-12-30 Puppet Labs - 2.1.2 + +* Documentation only release for PE 2.0.x + +##### 2011-11-08 Puppet Labs - 2.2.0 + +* #10285 - Refactor json to use pson instead. +* Maint - Add watchr autotest script +* Maint - Make rspec tests work with Puppet 2.6.4 +* #9859 - Add root\_home fact and tests + +##### 2011-08-18 Puppet Labs - 2.1.1 + +* Change facts.d paths to match Facter 2.0 paths. +* /etc/facter/facts.d +* /etc/puppetlabs/facter/facts.d + +##### 2011-08-17 Puppet Labs - 2.1.0 + +* Add R.I. Pienaar's facts.d custom facter fact +* facts defined in /etc/facts.d and /etc/puppetlabs/facts.d are + automatically loaded now. + +##### 2011-08-04 Puppet Labs - 2.0.0 + +* Rename whole\_line to file\_line +* This is an API change and as such motivating a 2.0.0 release according to semver.org. + +##### 2011-08-04 Puppet Labs - 1.1.0 + +* Rename append\_line to whole\_line +* This is an API change and as such motivating a 1.1.0 release. + +##### 2011-08-04 Puppet Labs - 1.0.0 + +* Initial stable release +* Add validate\_array and validate\_string functions +* Make merge() function work with Ruby 1.8.5 +* Add hash merging function +* Add has\_key function +* Add loadyaml() function +* Add append\_line native + +##### 2011-06-21 Jeff McCune - 0.1.7 + +* Add validate\_hash() and getvar() functions + +##### 2011-06-15 Jeff McCune - 0.1.6 + +* Add anchor resource type to provide containment for composite classes + +##### 2011-06-03 Jeff McCune - 0.1.5 + +* Add validate\_bool() function to stdlib + +##### 0.1.4 2011-05-26 Jeff McCune + +* Move most stages after main + +##### 0.1.3 2011-05-25 Jeff McCune + +* Add validate\_re() function + +##### 0.1.2 2011-05-24 Jeff McCune + +* Update to add annotated tag + +##### 0.1.1 2011-05-24 Jeff McCune + +* Add stdlib::stages class with a standard set of stages diff --git a/environments/prod/modules/stdlib/CONTRIBUTING.md b/environments/prod/modules/stdlib/CONTRIBUTING.md new file mode 100644 index 0000000..990edba --- /dev/null +++ b/environments/prod/modules/stdlib/CONTRIBUTING.md @@ -0,0 +1,217 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% bundle exec rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% bundle exec rake spec_clean +% bundle exec rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppet.com/community/get-help) + +* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) diff --git a/environments/prod/modules/stdlib/Gemfile b/environments/prod/modules/stdlib/Gemfile new file mode 100644 index 0000000..5d86325 --- /dev/null +++ b/environments/prod/modules/stdlib/Gemfile @@ -0,0 +1,86 @@ +#This file is generated by ModuleSync, do not edit. + +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +# Determines what type of gem is requested based on place_or_version. +def gem_type(place_or_version) + if place_or_version =~ /^git:/ + :git + elsif place_or_version =~ /^file:/ + :file + else + :gem + end +end + +# Find a location or specific version for a gem. place_or_version can be a +# version, which is most often used. It can also be git, which is specified as +# `git://somewhere.git#branch`. You can also use a file source location, which +# is specified as `file://some/location/on/disk`. +def location_for(place_or_version, fake_version = nil) + if place_or_version =~ /^(git[:@][^#]*)#(.*)/ + [fake_version, { :git => $1, :branch => $2, :require => false }].compact + elsif place_or_version =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [place_or_version, { :require => false }] + end +end + +# Used for gem conditionals +supports_windows = false + +group :development do + gem 'puppet-lint', :require => false + gem 'metadata-json-lint', :require => false, :platforms => 'ruby' + gem 'puppet_facts', :require => false + gem 'puppet-blacksmith', '>= 3.4.0', :require => false, :platforms => 'ruby' + gem 'puppetlabs_spec_helper', '>= 1.2.1', :require => false + gem 'rspec-puppet', '>= 2.3.2', :require => false + gem 'rspec-puppet-facts', :require => false, :platforms => 'ruby' + gem 'mocha', '< 1.2.0', :require => false + gem 'simplecov', :require => false, :platforms => 'ruby' + gem 'parallel_tests', '< 2.10.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'parallel_tests', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0') + gem 'rubocop', '0.41.2', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'rubocop', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0') + gem 'rubocop-rspec', '~> 1.6', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0') + gem 'pry', :require => false + gem 'json_pure', '<= 2.0.1', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'fast_gettext', '1.1.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0') + gem 'fast_gettext', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0') + gem 'rainbow', '< 2.2.0', :require => false +end + +group :system_tests do + gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '>= 3') + gem 'beaker-pe', :require => false + gem 'beaker-rspec', *location_for(ENV['BEAKER_RSPEC_VERSION']) + gem 'beaker-puppet_install_helper', :require => false + gem 'beaker-module_install_helper', :require => false + gem 'master_manipulator', :require => false + gem 'beaker-hostgenerator', *location_for(ENV['BEAKER_HOSTGENERATOR_VERSION']) + gem 'beaker-abs', *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.1') +end + +gem 'puppet', *location_for(ENV['PUPPET_GEM_VERSION']) + +# Only explicitly specify Facter/Hiera if a version has been specified. +# Otherwise it can lead to strange bundler behavior. If you are seeing weird +# gem resolution behavior, try setting `DEBUG_RESOLVER` environment variable +# to `1` and then run bundle install. +gem 'facter', *location_for(ENV['FACTER_GEM_VERSION']) if ENV['FACTER_GEM_VERSION'] +gem 'hiera', *location_for(ENV['HIERA_GEM_VERSION']) if ENV['HIERA_GEM_VERSION'] + + +# Evaluate Gemfile.local if it exists +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end + +# Evaluate ~/.gemfile if it exists +if File.exists?(File.join(Dir.home, '.gemfile')) + eval(File.read(File.join(Dir.home, '.gemfile')), binding) +end + +# vim:ft=ruby diff --git a/environments/prod/modules/stdlib/LICENSE b/environments/prod/modules/stdlib/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/environments/prod/modules/stdlib/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/environments/prod/modules/stdlib/MAINTAINERS.md b/environments/prod/modules/stdlib/MAINTAINERS.md new file mode 100644 index 0000000..e8004a5 --- /dev/null +++ b/environments/prod/modules/stdlib/MAINTAINERS.md @@ -0,0 +1,6 @@ +## Maintenance + +Maintainers: + - Puppet Forge Modules Team `forge-modules |at| puppet |dot| com` + +Tickets: https://tickets.puppet.com/browse/MODULES. Make sure to set component to `stdlib`. diff --git a/environments/prod/modules/stdlib/NOTICE b/environments/prod/modules/stdlib/NOTICE new file mode 100644 index 0000000..3c8c03a --- /dev/null +++ b/environments/prod/modules/stdlib/NOTICE @@ -0,0 +1,23 @@ +stdlib puppet module + +Copyright (C) 2011-2016 Puppet Labs, Inc. + +and some parts: + +Copyright (C) 2011 Krzysztof Wilczynski + + +Puppet Labs can be contacted at: info@puppetlabs.com + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/environments/prod/modules/stdlib/README.markdown b/environments/prod/modules/stdlib/README.markdown new file mode 100644 index 0000000..03daae9 --- /dev/null +++ b/environments/prod/modules/stdlib/README.markdown @@ -0,0 +1,1791 @@ +# stdlib + +#### Table of Contents + +1. [Overview](#overview) +2. [Module Description - What the module does and why it is useful](#module-description) +3. [Setup - The basics of getting started with stdlib](#setup) +4. [Usage - Configuration options and additional functionality](#usage) +5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +5. [Limitations - OS compatibility, etc.](#limitations) +6. [Development - Guide for contributing to the module](#development) + +## Overview + +Adds a standard library of resources for Puppet modules. + +## Module Description + +This module provides a standard library of resources for the development of Puppet modules. Puppet modules make heavy use of this standard library. The stdlib module adds the following resources to Puppet: + + * Stages + * Facts + * Functions + * Defined resource types + * Data Types + * Providers + +> *Note:* As of version 3.7, Puppet Enterprise no longer includes the stdlib module. If you're running Puppet Enterprise, you should install the most recent release of stdlib for compatibility with Puppet modules. + +## Setup + +Installing the stdlib module adds the functions, facts, and resources of this standard library to Puppet. + +## Usage + +After you've installed stdlib, all of its functions, facts, and resources are available for module use or development. + +If you want to use a standardized set of run stages for Puppet, `include stdlib` in your manifest. + +* `stdlib`: Most of stdlib's features are automatically loaded by Puppet. To use standardized run stages in Puppet, declare this class in your manifest with `include stdlib`. + + When declared, stdlib declares all other classes in the module. The only other class currently included in the module is `stdlib::stages`. + +The `stdlib::stages` class declares various run stages for deploying infrastructure, language runtimes, and application layers. The high level stages are (in order): + + * setup + * main + * runtime + * setup_infra + * deploy_infra + * setup_app + * deploy_app + * deploy + + Sample usage: + + ~~~ + node default { + include stdlib + class { java: stage => 'runtime' } + } + ~~~ + +## Reference + +### Classes + +#### Public Classes + + The stdlib class has no parameters. + +#### Private Classes + +* `stdlib::stages`: Manages a standard set of run stages for Puppet. It is managed by the stdlib class and should not be declared independently. + +### Resource Types + +#### `file_line` + +Ensures that a given line is contained within a file. The implementation matches the full line, including whitespace at the beginning and end. If the line is not contained in the given file, Puppet appends the line to the end of the file to ensure the desired state. Multiple resources can be declared to manage multiple lines in the same file. + +Example: + + file_line { 'sudo_rule': + path => '/etc/sudoers', + line => '%sudo ALL=(ALL) ALL', + } + + file_line { 'sudo_rule_nopw': + path => '/etc/sudoers', + line => '%sudonopw ALL=(ALL) NOPASSWD: ALL', + } + +In this example, Puppet ensures that both of the specified lines are contained in the file `/etc/sudoers`. + +Match Example: + + file_line { 'bashrc_proxy': + ensure => present, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + } + +In this code example, `match` looks for a line beginning with export followed by HTTP_PROXY and replaces it with the value in line. + +Match Example With `ensure => absent`: + + file_line { 'bashrc_proxy': + ensure => absent, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + match_for_absence => true, + } + +In this code example, `match` looks for a line beginning with export +followed by HTTP_PROXY and delete it. If multiple lines match, an +error will be raised unless the `multiple => true` parameter is set. + +Encoding example: + + file_line { "XScreenSaver": + ensure => present, + path => '/root/XScreenSaver' + line => "*lock: 10:00:00", + match => '^*lock:', + encoding => "iso-8859-1", + } + +Files with special characters that are not valid UTF-8 will give the +error message "invalid byte sequence in UTF-8". In this case, determine +the correct file encoding and specify the correct encoding using the +encoding attribute, the value of which needs to be a valid Ruby character +encoding. + +**Autorequires:** If Puppet is managing the file that contains the line being managed, the `file_line` resource autorequires that file. + +##### Parameters + +All parameters are optional, unless otherwise noted. + +* `after`: Specifies the line after which Puppet adds any new lines using a regular expression. (Existing lines are added in place.) Valid options: String containing a regex. Default: Undefined. +* `ensure`: Ensures whether the resource is present. Valid options: 'present', 'absent'. Default: 'present'. +* `line`: **Required.** Sets the line to be added to the file located by the `path` parameter. Valid options: String. Default: Undefined. +* `match`: Specifies a regular expression to run against existing lines in the file; if a match is found, it is replaced rather than adding a new line. A regex comparison is performed against the line value, and if it does not match, an exception is raised. Valid options: String containing a regex. Default: Undefined. +* `match_for_absence`: An optional value to determine if match should be applied when `ensure => absent`. If set to true and match is set, the line that matches match will be deleted. If set to false (the default), match is ignored when `ensure => absent` and the value of `line` is used instead. Ignored when `ensure => present`. Default: false. +* `multiple`: Determines if `match` and/or `after` can change multiple lines. If set to false, an exception will be raised if more than one line matches. Valid options: 'true', 'false'. Default: Undefined. +* `name`: Sets the name to use as the identity of the resource. This is necessary if you want the resource namevar to differ from the supplied `title` of the resource. Valid options: String. Default: Undefined. +* `path`: **Required.** Defines the file in which Puppet will ensure the line specified by `line`. Must be an absolute path to the file. +* `replace`: Defines whether the resource will overwrite an existing line that matches the `match` parameter. If set to false and a line is found matching the `match` param, the line will not be placed in the file. Valid options: true, false, yes, no. Default: true + +### Data Types + +#### `Stdlib::Absolutepath` + +A strict absolute path type. Uses a Variant of Unixpath and Windowspath types. + +Acceptable input examples: /var/log + /usr2/username/bin:/usr/local/bin:/usr/bin:. + C:\\WINDOWS\\System32 +Unacceptable input example: ../relative_path + +#### `Stdlib::Httpsurl` + +Matches https URLs. + +Acceptable input example: https://hello.com +Unacceptable input example: httds://notquiteright.org + +#### `Stdlib::Httpurl` + +Matches both https and http URLs. + +Acceptable input example: https://hello.com + http://hello.com +Unacceptable input example: httds://notquiteright.org + +#### `Stdlib::Unixpath` + +Matches paths on Unix type Operating Systems. + +Acceptable input example: /usr2/username/bin:/usr/local/bin:/usr/bin:. + /var/tmp +Unacceptable input example: C:/whatever + +#### `Stdlib::Windowspath` + +Matches paths on Windows Operating systems. + +Acceptable input example: C:\\WINDOWS\\System32 + C:\\ + \\\\host\\windows +Unacceptable input example: /usr2/username/bin:/usr/local/bin:/usr/bin:. + +### Functions + +#### `abs` + +Returns the absolute value of a number; for example, '-34.56' becomes '34.56'. Takes a single integer and float value as an argument. *Type*: rvalue. + +#### `any2array` + +Converts any object to an array containing that object. Empty argument lists are converted to an empty array. Arrays are left untouched. Hashes are converted to arrays of alternating keys and values. *Type*: rvalue. + +#### `base64` + +Converts a string to and from base64 encoding. Requires an `action` ('encode', 'decode') and either a plain or base64-encoded `string`, and an optional `method` ('default', 'strict', 'urlsafe') + +For backward compatibility, `method` will be set as `default` if not specified. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +*Examples:* +~~~ +base64('encode', 'hello') +base64('encode', 'hello', 'default') +# return: "aGVsbG8=\n" + +base64('encode', 'hello', 'strict') +# return: "aGVsbG8=" + +base64('decode', 'aGVsbG8=') +base64('decode', 'aGVsbG8=\n') +base64('decode', 'aGVsbG8=', 'default') +base64('decode', 'aGVsbG8=\n', 'default') +base64('decode', 'aGVsbG8=', 'strict') +# return: "hello" + +base64('encode', 'https://puppetlabs.com', 'urlsafe') +# return: "aHR0cHM6Ly9wdXBwZXRsYWJzLmNvbQ==" + +base64('decode', 'aHR0cHM6Ly9wdXBwZXRsYWJzLmNvbQ==', 'urlsafe') +# return: "https://puppetlabs.com" +~~~ + +*Type*: rvalue. + +#### `basename` + +Returns the `basename` of a path (optionally stripping an extension). For example: + * ('/path/to/a/file.ext') returns 'file.ext' + * ('relative/path/file.ext') returns 'file.ext' + * ('/path/to/a/file.ext', '.ext') returns 'file' + +*Type*: rvalue. + +#### `bool2num` + +Converts a boolean to a number. Converts values: + * 'false', 'f', '0', 'n', and 'no' to 0. + * 'true', 't', '1', 'y', and 'yes' to 1. + Requires a single boolean or string as an input. *Type*: rvalue. + +#### `bool2str` + +Converts a boolean to a string using optionally supplied arguments. The optional second and third arguments represent what true and false are converted to respectively. If only one argument is given, it is converted from a boolean to a string containing 'true' or 'false'. + +*Examples:* +~~~ +bool2str(true) => 'true' +bool2str(true, 'yes', 'no') => 'yes' +bool2str(false, 't', 'f') => 'f' +~~~ + +Requires a single boolean as input. *Type*: rvalue. + +#### `camelcase` + +Converts the case of a string or all strings in an array to camel case. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `capitalize` + +Capitalizes the first character of a string or array of strings and lowercases the remaining characters of each string. Requires either a single string or an array as an input. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `ceiling` + +Returns the smallest integer greater than or equal to the argument. Takes a single numeric value as an argument. *Type*: rvalue. + +#### `chomp` + +Removes the record separator from the end of a string or an array of strings; for example, 'hello\n' becomes 'hello'. Requires a single string or array as an input. *Type*: rvalue. + +#### `chop` + +Returns a new string with the last character removed. If the string ends with '\r\n', both characters are removed. Applying `chop` to an empty string returns an empty string. If you want to merely remove record separators, then you should use the `chomp` function. Requires a string or an array of strings as input. *Type*: rvalue. + +#### `clamp` + +Keeps value within the range [Min, X, Max] by sort based on integer value (order of params doesn't matter). Takes strings, arrays or numerics. Strings are converted and compared numerically. Arrays of values are flattened into a list for further handling. For example: + * `clamp('24', [575, 187])` returns 187. + * `clamp(16, 88, 661)` returns 88. + * `clamp([4, 3, '99'])` returns 4. + *Type*: rvalue. + +#### `concat` + +Appends the contents of multiple arrays onto the first array given. For example: + * `concat(['1','2','3'],'4')` returns ['1','2','3','4']. + * `concat(['1','2','3'],'4',['5','6','7'])` returns ['1','2','3','4','5','6','7']. + *Type*: rvalue. + +#### `convert_base` + +Converts a given integer or base 10 string representing an integer to a specified base, as a string. For example: + * `convert_base(5, 2)` results in: '101' + * `convert_base('254', '16')` results in: 'fe' + +#### `count` + +If called with only an array, it counts the number of elements that are **not** nil/undef. If called with a second argument, counts the number of elements in an array that matches the second argument. *Type*: rvalue. + +#### `deep_merge` + +Recursively merges two or more hashes together and returns the resulting hash. + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + +The resulting hash is equivalent to: + $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + +When there is a duplicate key that is a hash, they are recursively merged. When there is a duplicate key that is not a hash, the key in the rightmost hash will "win.". +*Type*: rvalue, rvalue. + +#### `defined_with_params` + +Takes a resource reference and an optional hash of attributes. Returns 'true' if a resource with the specified attributes has already been added to the catalog. Returns 'false' otherwise. + + ~~~ + user { 'dan': + ensure => present, + } + + if ! defined_with_params(User[dan], {'ensure' => 'present' }) { + user { 'dan': ensure => present, } + } + ~~~ + +*Type*: rvalue. + +#### `delete` + +Deletes all instances of a given element from an array, substring from a string, or key from a hash. + +For example, `delete(['a','b','c','b'], 'b')` returns ['a','c']; `delete('abracadabra', 'bra')` returns 'acada'. `delete({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete(['ab', 'b'], 'b')` returns ['ab']. + +*Type*: rvalue. + +#### `delete_at` + +Deletes a determined indexed value from an array. For example, `delete_at(['a','b','c'], 1)` returns ['a','c']. *Type*: rvalue. + +#### `delete_regex` + +Deletes all instances of a given element from an array or hash that match a provided regular expression. A string will be treated as a one-item array. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +For example, `delete_regex(['a','b','c','b'], 'b')` returns ['a','c']; `delete_regex({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete_regex(['abf', 'ab', 'ac'], '^ab.*')` returns ['ac']. `delete_regex(['ab', 'b'], 'b')` returns ['ab']. + +*Type*: rvalue. + +#### `delete_values` + +Deletes all instances of a given value from a hash. For example, `delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B')` returns {'a'=>'A','c'=>'C','B'=>'D'} *Type*: rvalue. + +#### `delete_undef_values` + +Deletes all instances of the undef value from an array or hash. For example, `$hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})` returns {a => 'A', b => '', d => false}. *Type*: rvalue. + +#### `deprecation` + +Prints deprecation warnings and logs a warning once for a given key: + +``` +deprecation(key, message) +``` + +* key: to keep the number of messages low, during the lifetime of a puppet process, only one message per key is logged. +* message: the text to be logged. + +The Puppet settings '[disable_warnings](https://docs.puppet.com/puppet/latest/reference/configuration.html#disablewarnings)', '[max_deprecations](https://docs.puppet.com/puppet/latest/reference/configuration.html#maxdeprecations)', and '[strict](https://docs.puppet.com/puppet/latest/reference/configuration.html#strict)' affect this function. Set 'strict' to `error` to fail immediately with the deprecation message, `off` to output emit no messages at all, or `warning` (default) to log all warnings. + +Additionally you can set the environment variable `STDLIB_LOG_DEPRECATIONS` to decide whether or not to log deprecation warnings: if this environment variable is set to `true`, the functions log a warning, if it is set to `false`, no warnings are logged. If no value is set at all, Puppet 4 will emit warnings, while Puppet 3 will not. Using this setting is especially useful for automated tests to avoid flooding your logs before you are ready to migrate. + +*Type*: String, String. + +#### `difference` + +Returns the difference between two arrays. The returned array is a copy of the original array, removing any items that also appear in the second array. For example, `difference(["a","b","c"],["b","c","d"])` returns ["a"]. *Type*: rvalue. + +#### `dig` + +DEPRECATED: This function has been replaced in Puppet 4.5.0, use dig44() for backwards compatibility or use the new version. + +*Type*: rvalue. + +Retrieves a value within multiple layers of hashes and arrays via an array of keys containing a path. The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig($data, ['a', 'b', 2]) +# $value = 'b3' + +# with all possible options +$value = dig($data, ['a', 'b', 2], 'not_found') +# $value = 'b3' + +# using the default value +$value = dig($data, ['a', 'b', 'c', 'd'], 'not_found') +# $value = 'not_found' +~~~ + +1. **$data** The data structure we are working with. +2. **['a', 'b', 2]** The path array. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) + +#### `dig44` + +*Type*: rvalue. + +Retrieves a value within multiple layers of hashes and arrays via an array of keys containing a path. The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig44($data, ['a', 'b', 2]) +# $value = 'b3' + +# with all possible options +$value = dig44($data, ['a', 'b', 2], 'not_found') +# $value = 'b3' + +# using the default value +$value = dig44($data, ['a', 'b', 'c', 'd'], 'not_found') +# $value = 'not_found' +~~~ + +1. **$data** The data structure we are working with. +2. **['a', 'b', 2]** The path array. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) + +#### `dirname` + +Returns the `dirname` of a path. For example, `dirname('/path/to/a/file.ext')` returns '/path/to/a'. *Type*: rvalue. + +#### `dos2unix` + +Returns the Unix version of the given string. Very useful when using a File resource with a cross-platform template. *Type*: rvalue. + +~~~ +file{$config_file: + ensure => file, + content => dos2unix(template('my_module/settings.conf.erb')), +} +~~~ + +See also [unix2dos](#unix2dos). + +#### `downcase` + +Converts the case of a string or of all strings in an array to lowercase. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `empty` + +Returns true if the argument is an array or hash that contains no elements, or an empty string. Returns false when the argument is a numerical value. *Type*: rvalue. + +#### `enclose_ipv6` + +Takes an array of ip addresses and encloses the ipv6 addresses with square brackets. *Type*: rvalue. + +#### `ensure_packages` + +Takes a list of packages array/hash and only installs them if they don't already exist. It optionally takes a hash as a second parameter to be passed as the third argument to the `ensure_resource()` or `ensure_resources()` function. *Type*: statement. + +For Array: + + ensure_packages(['ksh','openssl'], {'ensure' => 'present'}) + +For Hash: + + ensure_packages({'ksh' => { ensure => '20120801-1' } , 'mypackage' => { source => '/tmp/myrpm-1.0.0.x86_64.rpm', provider => "rpm" }}, {'ensure' => 'present'}) + +#### `ensure_resource` + +Takes a resource type, title, and a hash of attributes that describe the resource(s). + +~~~ +user { 'dan': + ensure => present, +} +~~~ + +This example only creates the resource if it does not already exist: + + `ensure_resource('user', 'dan', {'ensure' => 'present' })` + +If the resource already exists, but does not match the specified parameters, this function attempts to recreate the resource, leading to a duplicate resource definition error. + +An array of resources can also be passed in, and each will be created with the type and parameters specified if it doesn't already exist. + + `ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})` + +*Type*: statement. + +#### `ensure_resources` + +Takes a resource type, title (only hash), and a hash of attributes that describe the resource(s). + +~~~ +user { 'dan': + gid => 'mygroup', + ensure => present, +} + +ensure_resources($user) +~~~ + +An hash of resources should be passed in and each will be created with the type and parameters specified if it doesn't already exist: + + ensure_resources('user', {'dan' => { gid => 'mygroup', uid => '600' } , 'alex' => { gid => 'mygroup' }}, {'ensure' => 'present'}) + +From Hiera Backend: + +~~~ +userlist: + dan: + gid: 'mygroup' + uid: '600' + alex: + gid: 'mygroup' + +ensure_resources('user', hiera_hash('userlist'), {'ensure' => 'present'}) +~~~ + +### `flatten` + +Flattens deeply nested arrays and returns a single flat array as a result. For example, `flatten(['a', ['b', ['c']]])` returns ['a','b','c']. *Type*: rvalue. + +#### `floor` + +Takes a single numeric value as an argument, and returns the largest integer less than or equal to the argument. *Type*: rvalue. + +#### `fqdn_rand_string` + +Generates a random alphanumeric string using an optionally-specified character set (default is alphanumeric), combining the `$fqdn` fact and an optional seed for repeatable randomness. + +*Usage:* +~~~ +fqdn_rand_string(LENGTH, [CHARSET], [SEED]) +~~~ +*Examples:* +~~~ +fqdn_rand_string(10) +fqdn_rand_string(10, 'ABCDEF!@#$%^') +fqdn_rand_string(10, '', 'custom seed') +~~~ + +*Type*: rvalue. + +#### `fqdn_rotate` + +Rotates an array or string a random number of times, combining the `$fqdn` fact and an optional seed for repeatable randomness. + +*Usage:* + +~~~ +fqdn_rotate(VALUE, [SEED]) +~~~ + +*Examples:* + +~~~ +fqdn_rotate(['a', 'b', 'c', 'd']) +fqdn_rotate('abcd') +fqdn_rotate([1, 2, 3], 'custom seed') +~~~ + +*Type*: rvalue. + +#### `fqdn_uuid` + +Returns a rfc4122 valid version 5 UUID based on an FQDN string under the DNS namespace + + * fqdn_uuid('puppetlabs.com') returns '9c70320f-6815-5fc5-ab0f-debe68bf764c' + * fqdn_uuid('google.com') returns '64ee70a4-8cc1-5d25-abf2-dea6c79a09c8' + +*Type*: rvalue. + +#### `get_module_path` + +Returns the absolute path of the specified module for the current environment. + + `$module_path = get_module_path('stdlib')` + +*Type*: rvalue. + +#### `getparam` + +Takes a resource reference and the name of the parameter, and returns the value of the resource's parameter. + +For example, the following returns 'param_value': + + ~~~ + define example_resource($param) { + } + + example_resource { "example_resource_instance": + param => "param_value" + } + + getparam(Example_resource["example_resource_instance"], "param") + ~~~ + +*Type*: rvalue. + +#### `getvar` + +Looks up a variable in a remote namespace. + +For example: + + ~~~ + $foo = getvar('site::data::foo') + # Equivalent to $foo = $site::data::foo + ~~~ + +This is useful if the namespace itself is stored in a string: + + ~~~ + $datalocation = 'site::data' + $bar = getvar("${datalocation}::bar") + # Equivalent to $bar = $site::data::bar + ~~~ + +*Type*: rvalue. + +#### `grep` + +Searches through an array and returns any elements that match the provided regular expression. For example, `grep(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['aaa','aaaddd']. *Type*: rvalue. + +#### `has_interface_with` + +Returns a boolean based on kind and value: + * macaddress + * netmask + * ipaddress + * network + +*Examples:* + + ~~~ + has_interface_with("macaddress", "x:x:x:x:x:x") + has_interface_with("ipaddress", "127.0.0.1") => true + ~~~ + +If no kind is given, then the presence of the interface is checked: + + ~~~ + has_interface_with("lo") => true + ~~~ + +*Type*: rvalue. + +#### `has_ip_address` + +Returns 'true' if the client has the requested IP address on some interface. This function iterates through the `interfaces` fact and checks the `ipaddress_IFACE` facts, performing a simple string comparison. *Type*: rvalue. + +#### `has_ip_network` + +Returns 'true' if the client has an IP address within the requested network. This function iterates through the `interfaces` fact and checks the `network_IFACE` facts, performing a simple string comparision. *Type*: rvalue. + +#### `has_key` + +Determines if a hash has a certain key value. + +*Example*: + + ~~~ + $my_hash = {'key_one' => 'value_one'} + if has_key($my_hash, 'key_two') { + notice('we will not reach here') + } + if has_key($my_hash, 'key_one') { + notice('this will be printed') + } + ~~~ + +*Type*: rvalue. + +#### `hash` + +Converts an array into a hash. For example, `hash(['a',1,'b',2,'c',3])` returns {'a'=>1,'b'=>2,'c'=>3}. *Type*: rvalue. + +#### `intersection` + +Returns an array an intersection of two. For example, `intersection(["a","b","c"],["b","c","d"])` returns ["b","c"]. *Type*: rvalue. + +#### `is_a` + +Boolean check to determine whether a variable is of a given data type. This is equivalent to the `=~` type checks. This function is available only in Puppet 4 or in Puppet 3 with the "future" parser. + + ~~~ + foo = 3 + $bar = [1,2,3] + $baz = 'A string!' + + if $foo.is_a(Integer) { + notify { 'foo!': } + } + if $bar.is_a(Array) { + notify { 'bar!': } + } + if $baz.is_a(String) { + notify { 'baz!': } + } + ~~~ + +See the [the Puppet type system](https://docs.puppetlabs.com/references/latest/type.html#about-resource-types) for more information about types. +See the [`assert_type()`](https://docs.puppetlabs.com/references/latest/function.html#asserttype) function for flexible ways to assert the type of a value. + +#### `is_absolute_path` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the given path is absolute. *Type*: rvalue. + +#### `is_array` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is an array. *Type*: rvalue. + +#### `is_bool` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a boolean. *Type*: rvalue. + +#### `is_domain_name` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a syntactically correct domain name. *Type*: rvalue. + +#### `is_float` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a float. *Type*: rvalue. + +#### `is_function_available` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Accepts a string as an argument and determines whether the Puppet runtime has access to a function by that name. It returns 'true' if the function exists, 'false' if not. *Type*: rvalue. + +#### `is_hash` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a hash. *Type*: rvalue. + +#### `is_integer` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable returned to this string is an integer. *Type*: rvalue. + +#### `is_ip_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IP address. *Type*: rvalue. + +#### `is_ipv6_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IPv6 address. *Type*: rvalue. + +#### `is_ipv4_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IPv4 address. *Type*: rvalue. + +#### `is_mac_address` + +Returns 'true' if the string passed to this function is a valid MAC address. *Type*: rvalue. + +#### `is_numeric` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a number. *Type*: rvalue. + +#### `is_string` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a string. *Type*: rvalue. + +#### `join` + +Joins an array into a string using a separator. For example, `join(['a','b','c'], ",")` results in: "a,b,c". *Type*: rvalue. + +#### `join_keys_to_values` + +Joins each key of a hash to that key's corresponding value with a separator. Keys are cast to strings. +If values are arrays, multiple keys are added for each element. +The return value is an array in which each element is one joined key/value pair. For example, `join_keys_to_values({'a'=>1,'b'=>[2,3]}, " is ")` results in ["a is 1","b is 2","b is 3"]. *Type*: rvalue. + +#### `keys` + +Returns the keys of a hash as an array. *Type*: rvalue. + +#### `length` + +Returns the length of a given string, array or hash. To eventually replace the deprecated size() function as can handle the new type functionality introduced in Puppet 4. *Type*: rvalue. + +#### `loadyaml` + +Loads a YAML file containing an array, string, or hash, and returns the data in the corresponding native data type. + +For example: + + ~~~ + $myhash = loadyaml('/etc/puppet/data/myhash.yaml') + ~~~ + +The second parameter will be returned if the file was not found or could not be parsed. + +For example: + + ~~~ + $myhash = loadyaml('no-file.yaml', {'default'=>'value'}) + ~~~ + +*Type*: rvalue. + +#### `loadjson` + +Loads a JSON file containing an array, string, or hash, and returns the data in the corresponding native data type. + +For example: + + ~~~ + $myhash = loadjson('/etc/puppet/data/myhash.json') + ~~~ + +The second parameter will be returned if the file was not found or could not be parsed. + +For example: + + ~~~ + $myhash = loadjson('no-file.json', {'default'=>'value'}) + ~~~ + +*Type*: rvalue. + +#### `load_module_metadata` + +Loads the metadata.json of a target module. Can be used to determine module version and authorship for dynamic support of modules. + + ~~~ + $metadata = load_module_metadata('archive') + notify { $metadata['author']: } + ~~~ + +If you do not want to fail the catalog compilation when a module's metadata file is absent: + + ~~~ + $metadata = load_module_metadata('mysql', true) + if empty($metadata) { + notify { "This module does not have a metadata.json file.": } + } + ~~~ + +*Type*: rvalue. + +#### `lstrip` + +Strips spaces to the left of a string. *Type*: rvalue. + +#### `max` + +Returns the highest value of all arguments. Requires at least one argument. *Type*: rvalue. + +#### `member` + +This function determines if a variable is a member of an array. The variable can be either a string, array, or fixnum. For example, `member(['a','b'], 'b')` and `member(['a','b','c'], ['b','c'])` return 'true', while `member(['a','b'], 'c')` and `member(['a','b','c'], ['c','d'])` return 'false'. *Note*: This function does not support nested arrays. If the first argument contains nested arrays, it will not recurse through them. + +*Type*: rvalue. + +#### `merge` + +Merges two or more hashes together and returns the resulting hash. + +*Example*: + + ~~~ + $hash1 = {'one' => 1, 'two' => 2} + $hash2 = {'two' => 'dos', 'three' => 'tres'} + $merged_hash = merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'} + ~~~ + +When there is a duplicate key, the key in the rightmost hash "wins." *Type*: rvalue. + +#### `min` + +Returns the lowest value of all arguments. Requires at least one argument. *Type*: rvalue. + +#### `num2bool` + +Converts a number or a string representation of a number into a true boolean. Zero or anything non-numeric becomes 'false'. Numbers greater than 0 become 'true'. *Type*: rvalue. + +#### `parsejson` + +Converts a string of JSON into the correct Puppet structure. *Type*: rvalue. The optional second argument is returned if the data was not correct. + +#### `parseyaml` + +Converts a string of YAML into the correct Puppet structure. *Type*: rvalue. The optional second argument is returned if the data was not correct. + +#### `pick` + +From a list of values, returns the first value that is not undefined or an empty string. Takes any number of arguments, and raises an error if all values are undefined or empty. + + ~~~ + $real_jenkins_version = pick($::jenkins_version, '1.449') + ~~~ + +*Type*: rvalue. + +#### `pick_default` + +Returns the first value in a list of values. Contrary to the `pick()` function, the `pick_default()` does not fail if all arguments are empty. This allows it to use an empty value as default. *Type*: rvalue. + +#### `prefix` + +Applies a prefix to all elements in an array, or to the keys in a hash. +For example: +* `prefix(['a','b','c'], 'p')` returns ['pa','pb','pc'] +* `prefix({'a'=>'b','b'=>'c','c'=>'d'}, 'p')` returns {'pa'=>'b','pb'=>'c','pc'=>'d'}. + +*Type*: rvalue. + +#### `pry` + +This function invokes a pry debugging session in the current scope object. This is useful for debugging manifest code at specific points during a compilation. Should only be used when running `puppet apply` or running a puppet master in the foreground. This requires the `pry` gem to be installed in puppet's rubygems. + +*Examples:* +```puppet +pry() +``` +Once in a pry session, some interesting commands: + +* Run `catalog` to see the contents currently compiling catalog +* Run `cd catalog` and `ls` to see catalog methods and instance variables +* Run `@resource_table` to see the current catalog resource table + +#### `assert_private` + +Sets the current class or definition as private. Calling the class or definition from outside the current module will fail. + +For example, `assert_private()` called in class `foo::bar` outputs the following message if class is called from outside module `foo`: + + ~~~ + Class foo::bar is private + ~~~ + + To specify the error message you want to use: + + ~~~ + assert_private("You're not supposed to do that!") + ~~~ + +*Type*: statement. + +#### `pw_hash` + +Hashes a password using the crypt function. Provides a hash usable on most POSIX systems. + +The first argument to this function is the password to hash. If it is undef or an empty string, this function returns undef. + +The second argument to this function is which type of hash to use. It will be converted into the appropriate crypt(3) hash specifier. Valid hash types are: + +|Hash type |Specifier| +|---------------------|---------| +|MD5 |1 | +|SHA-256 |5 | +|SHA-512 (recommended)|6 | + +The third argument to this function is the salt to use. + +*Type*: rvalue. + +**Please note:** This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +**Note:** this uses the Puppet master's implementation of crypt(3). If your environment contains several different operating systems, ensure that they are compatible before using this function. + +#### `range` + +Extrapolates a range as an array when given in the form of '(start, stop)'. For example, `range("0", "9")` returns [0,1,2,3,4,5,6,7,8,9]. Zero-padded strings are converted to integers automatically, so `range("00", "09")` returns [0,1,2,3,4,5,6,7,8,9]. + +Non-integer strings are accepted; `range("a", "c")` returns ["a","b","c"], and `range("host01", "host10")` returns ["host01", "host02", ..., "host09", "host10"]. +NB Be explicit in including trailing zeros. Otherwise the underlying ruby function will fail. + +Passing a third argument will cause the generated range to step by that interval, e.g. `range("0", "9", "2")` returns ["0","2","4","6","8"]. + +*Type*: rvalue. + +#### `regexpescape` + +Regexp escape a string or array of strings. Requires either a single string or an array as an input. *Type*: rvalue. + +#### `reject` + +Searches through an array and rejects all elements that match the provided regular expression. For example, `reject(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['bbb','ccc']. *Type*: rvalue. + +#### `reverse` + +Reverses the order of a string or array. *Type*: rvalue. + +#### `rstrip` + +Strips spaces to the right of the string. *Type*: rvalue. + +#### `seeded_rand` + +Takes an integer max value and a string seed value and returns a repeatable random integer smaller than max. Like `fqdn_rand`, but does not add node specific data to the seed. *Type*: rvalue. + +#### `shell_escape` + +Escapes a string so that it can be safely used in a Bourne shell command line. Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single quotes. This function behaves the same as ruby's `Shellwords.shellescape()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellescape). + +*Example:* +~~~ +shell_escape('foo b"ar') => 'foo\ b\"ar' +~~~ + +*Type*: rvalue. + +#### `shell_join` + +Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are +then joined together, with a single space in between. This function behaves the same as ruby's `Shellwords.shelljoin()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shelljoin). + +*Example:* +~~~ +shell_join(['foo bar', 'ba"z']) => 'foo\ bar ba\"z' +~~~ + +*Type*: rvalue. + +#### `shell_split` + +Splits a string into an array of tokens in the same way the Bourne shell does. This function behaves the same as ruby's `Shellwords.shellsplit()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellsplit). + +*Example:* +~~~ +shell_split('foo\ bar ba\"z') => ['foo bar', 'ba"z'] +~~~ + +*Type*: rvalue. + +#### `shuffle` + +Randomizes the order of a string or array elements. *Type*: rvalue. + +#### `size` + +Returns the number of elements in a string, an array or a hash. May get confused around Puppet 4 type values and as such is to be deprecated in the next release and replaced with the new stdlib length function. *Type*: rvalue. + +#### `sort` + +Sorts strings and arrays lexically. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `squeeze` + +Returns a new string where runs of the same character that occur in this set are replaced by a single character. *Type*: rvalue. + +#### `str2bool` + +Converts certain strings to a boolean. This attempts to convert strings that contain the values '1', 'true', 't', 'y', or 'yes' to true. Strings that contain values '0', 'false', 'f', 'n', or 'no', or that are an empty string or undefined are converted to false. Any other value causes an error. These checks are case insensitive. *Type*: rvalue. + +#### `str2saltedsha512` + +Converts a string to a salted-SHA512 password hash, used for OS X versions >= 10.7. Given any string, this function returns a hex version of a salted-SHA512 password hash, which can be inserted into your Puppet +manifests as a valid password attribute. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `strftime` + +Returns formatted time. For example, `strftime("%s")` returns the time since Unix epoch, and `strftime("%Y-%m-%d")` returns the date. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + + *Format:* + + * `%a`: The abbreviated weekday name ('Sun') + * `%A`: The full weekday name ('Sunday') + * `%b`: The abbreviated month name ('Jan') + * `%B`: The full month name ('January') + * `%c`: The preferred local date and time representation + * `%C`: Century (20 in 2009) + * `%d`: Day of the month (01..31) + * `%D`: Date (%m/%d/%y) + * `%e`: Day of the month, blank-padded ( 1..31) + * `%F`: Equivalent to %Y-%m-%d (the ISO 8601 date format) + * `%h`: Equivalent to %b + * `%H`: Hour of the day, 24-hour clock (00..23) + * `%I`: Hour of the day, 12-hour clock (01..12) + * `%j`: Day of the year (001..366) + * `%k`: Hour, 24-hour clock, blank-padded ( 0..23) + * `%l`: Hour, 12-hour clock, blank-padded ( 0..12) + * `%L`: Millisecond of the second (000..999) + * `%m`: Month of the year (01..12) + * `%M`: Minute of the hour (00..59) + * `%n`: Newline (\n) + * `%N`: Fractional seconds digits, default is 9 digits (nanosecond) + * `%3N`: Millisecond (3 digits) + * `%6N`: Microsecond (6 digits) + * `%9N`: Nanosecond (9 digits) + * `%p`: Meridian indicator ('AM' or 'PM') + * `%P`: Meridian indicator ('am' or 'pm') + * `%r`: Time, 12-hour (same as %I:%M:%S %p) + * `%R`: Time, 24-hour (%H:%M) + * `%s`: Number of seconds since the Unix epoch, 1970-01-01 00:00:00 UTC. + * `%S`: Second of the minute (00..60) + * `%t`: Tab character ( ) + * `%T`: Time, 24-hour (%H:%M:%S) + * `%u`: Day of the week as a decimal, Monday being 1. (1..7) + * `%U`: Week number of the current year, starting with the first Sunday as the first day of the first week (00..53) + * `%v`: VMS date (%e-%b-%Y) + * `%V`: Week number of year according to ISO 8601 (01..53) + * `%W`: Week number of the current year, starting with the first Monday as the first day of the first week (00..53) + * `%w`: Day of the week (Sunday is 0, 0..6) + * `%x`: Preferred representation for the date alone, no time + * `%X`: Preferred representation for the time alone, no date + * `%y`: Year without a century (00..99) + * `%Y`: Year with century + * `%z`: Time zone as hour offset from UTC (e.g. +0900) + * `%Z`: Time zone name + * `%%`: Literal '%' character + +#### `strip` + +Removes leading and trailing whitespace from a string or from every string inside an array. For example, `strip(" aaa ")` results in "aaa". *Type*: rvalue. + +#### `suffix` + +Applies a suffix to all elements in an array, or to the keys in a hash. +For example: +* `suffix(['a','b','c'], 'p')` returns ['ap','bp','cp'] +* `suffix({'a'=>'b','b'=>'c','c'=>'d'}, 'p')` returns {'ap'=>'b','bp'=>'c','cp'=>'d'}. + +*Type*: rvalue. + +#### `swapcase` + +Swaps the existing case of a string. For example, `swapcase("aBcD")` results in "AbCd". *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `time` + +Returns the current Unix epoch time as an integer. For example, `time()` returns something like '1311972653'. *Type*: rvalue. + +#### `to_bytes` + +Converts the argument into bytes, for example "4 kB" becomes "4096". Takes a single string value as an argument. *Type*: rvalue. + +#### `try_get_value` + +*Type*: rvalue. + +DEPRECATED: replaced by `dig()`. + +Retrieves a value within multiple layers of hashes and arrays via a string containing a path. The path is a string of hash keys or array indexes starting with zero, separated by the path separator character (default "/"). The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. The last argument can set the path separator character. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = try_get_value($data, 'a/b/2') +# $value = 'b3' + +# with all possible options +$value = try_get_value($data, 'a/b/2', 'not_found', '/') +# $value = 'b3' + +# using the default value +$value = try_get_value($data, 'a/b/c/d', 'not_found') +# $value = 'not_found' + +# using custom separator +$value = try_get_value($data, 'a|b', [], '|') +# $value = ['b1','b2','b3'] +~~~ + +1. **$data** The data structure we are working with. +2. **'a/b/2'** The path string. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) +4. **'/'** The path separator character. + (optional, defaults to *'/'*) + +#### `type3x` + +Returns a string description of the type when passed a value. Type can be a string, array, hash, float, integer, or boolean. This function will be removed when Puppet 3 support is dropped and the new type system can be used. *Type*: rvalue. + +#### `type_of` + +This function is provided for backwards compatibility but is generally not preferred over the built-in [type() function](https://docs.puppet.com/puppet/latest/reference/function.html#type) provided by Puppet. + +Returns the literal type when passed a value. Requires the new parser. Useful for comparison of types with `<=` such as in `if type_of($some_value) <= Array[String] { ... }` (which is equivalent to `if $some_value =~ Array[String] { ... }`) *Type*: rvalue. + +#### `union` + +Returns a union of two or more arrays, without duplicates. For example, `union(["a","b","c"],["b","c","d"])` returns ["a","b","c","d"]. *Type*: rvalue. + +#### `unique` + +Removes duplicates from strings and arrays. For example, `unique("aabbcc")` returns 'abc', and `unique(["a","a","b","b","c","c"])` returns ["a","b","c"]. *Type*: rvalue. + +#### `unix2dos` + +Returns the DOS version of the given string. Very useful when using a File resource with a cross-platform template. *Type*: rvalue. + +~~~ +file{$config_file: + ensure => file, + content => unix2dos(template('my_module/settings.conf.erb')), +} +~~~ + +See also [dos2unix](#dos2unix). + +#### `upcase` + +Converts an object, array or hash of objects that respond to upcase to uppercase. For example, `upcase('abcd')` returns 'ABCD'. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `uriescape` + +URLEncodes a string or array of strings. Requires either a single string or an array as an input. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `validate_absolute_path` + +Validates that a given string represents an absolute path in the filesystem. Works for Windows and Unix style paths. + +The following values pass: + +~~~ +$my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' +validate_absolute_path($my_path) +$my_path2 = '/var/lib/puppet' +validate_absolute_path($my_path2) +$my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet'] +validate_absolute_path($my_path3) +$my_path4 = ['/var/lib/puppet','/usr/share/puppet'] +validate_absolute_path($my_path4) +~~~ + +The following values fail, causing compilation to abort: + +~~~ +validate_absolute_path(true) +validate_absolute_path('../var/lib/puppet') +validate_absolute_path('var/lib/puppet') +validate_absolute_path([ 'var/lib/puppet', '/var/foo' ]) +validate_absolute_path([ '/var/lib/puppet', 'var/foo' ]) +$undefined = undef +validate_absolute_path($undefined) +~~~ + +*Type*: statement. + +#### `validate_array` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are array data structures. Aborts catalog compilation if any value fails this check. + +The following values pass: + +~~~ +$my_array = [ 'one', 'two' ] +validate_array($my_array) +~~~ + +The following values fail, causing compilation to abort: + +~~~ +validate_array(true) +validate_array('some_string') +$undefined = undef +validate_array($undefined) +~~~ + +*Type*: statement. + +#### `validate_augeas` + +Performs validation of a string using an Augeas lens. The first argument of this function should be the string to test, and the second argument should be the name of the Augeas lens to use. If Augeas fails to parse the string with the lens, the compilation aborts with a parse error. + +A third optional argument lists paths which should **not** be found in the file. The `$file` variable points to the location of the temporary file being tested in the Augeas tree. + +For example, to make sure your $passwdcontent never contains user `foo`: + +~~~ +validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) +~~~ + +To ensure that no users use the '/bin/barsh' shell: + +~~~ +validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] +~~~ + +You can pass a fourth argument as the error message raised and shown to the user: + +~~~ +validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') +~~~ + +*Type*: statement. + +#### `validate_bool` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are either true or false. Aborts catalog compilation if any value fails this check. + +The following values will pass: + +~~~ +$iamtrue = true +validate_bool(true) +validate_bool(true, true, false, $iamtrue) +~~~ + +The following values will fail, causing compilation to abort: + +~~~ +$some_array = [ true ] +validate_bool("false") +validate_bool("true") +validate_bool($some_array) +~~~ + +*Type*: statement. + +#### `validate_cmd` + +Performs validation of a string with an external command. The first argument of this function should be a string to test, and the second argument should be a path to a test command taking a % as a placeholder for the file path (will default to the end of the command if no % placeholder given). If the command is launched against a tempfile containing the passed string, or returns a non-null value, compilation will abort with a parse error. + +If a third argument is specified, this will be the error message raised and seen by the user. + +~~~ +# Defaults to end of path +validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') +~~~ +~~~ +# % as file location +validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content') +~~~ + +*Type*: statement. + +#### `validate_hash` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are hash data structures. Aborts catalog compilation if any value fails this check. + + The following values will pass: + + ~~~ + $my_hash = { 'one' => 'two' } + validate_hash($my_hash) + ~~~ + + The following values will fail, causing compilation to abort: + + ~~~ + validate_hash(true) + validate_hash('some_string') + $undefined = undef + validate_hash($undefined) + ~~~ + +*Type*: statement. + +#### `validate_integer` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is an integer (or an array of integers). Aborts catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not an integer or array of integers, and if arg 2 and arg 3 are not convertable to an integer. + + The following values will pass: + + ~~~ + validate_integer(1) + validate_integer(1, 2) + validate_integer(1, 1) + validate_integer(1, 2, 0) + validate_integer(2, 2, 2) + validate_integer(2, '', 0) + validate_integer(2, undef, 0) + $foo = undef + validate_integer(2, $foo, 0) + validate_integer([1,2,3,4,5], 6) + validate_integer([1,2,3,4,5], 6, 0) + ~~~ + + * Plus all of the above, but any combination of values passed as strings ('1' or "1"). + * Plus all of the above, but with (correct) combinations of negative integer values. + + The following values will fail, causing compilation to abort: + + ~~~ + validate_integer(true) + validate_integer(false) + validate_integer(7.0) + validate_integer({ 1 => 2 }) + $foo = undef + validate_integer($foo) + validate_integer($foobaridontexist) + + validate_integer(1, 0) + validate_integer(1, true) + validate_integer(1, '') + validate_integer(1, undef) + validate_integer(1, , 0) + validate_integer(1, 2, 3) + validate_integer(1, 3, 2) + validate_integer(1, 3, true) + ~~~ + + * Plus all of the above, but any combination of values passed as strings ('false' or "false"). + * Plus all of the above, but with incorrect combinations of negative integer values. + * Plus all of the above, but with non-integer items in arrays or maximum / minimum argument. + + *Type*: statement. + +#### `validate_ip_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the argument is an IP address, regardless of it is an IPv4 or an IPv6 +address. It also validates IP address with netmask. The argument must be given as a string. + +The following values will pass: + + ~~~ + validate_ip_address('0.0.0.0') + validate_ip_address('8.8.8.8') + validate_ip_address('127.0.0.1') + validate_ip_address('194.232.104.150') + validate_ip_address('3ffe:0505:0002::') + validate_ip_address('::1/64') + validate_ip_address('fe80::a00:27ff:fe94:44d6/64') + validate_ip_address('8.8.8.8/32') + ~~~ + +The following values will fail, causing compilation to abort: + + ~~~ + validate_ip_address(1) + validate_ip_address(true) + validate_ip_address(0.0.0.256) + validate_ip_address('::1', {}) + validate_ip_address('0.0.0.0.0') + validate_ip_address('3.3.3') + validate_ip_address('23.43.9.22/64') + validate_ip_address('260.2.32.43') + ~~~ + + +#### `validate_legacy` + +Validates a value against both a specified type and a deprecated validation function. Silently passes if both pass, errors if only one validation passes, and fails if both validations return false. + +Accepts arguments for: +* the type to check the value against, +* the full name of the previous validation function, +* the value to be checked, +* an unspecified number of arguments needed for the previous validation function. + +Example: + +``` +validate_legacy("Optional[String]", "validate_re", "Value to be validated", ["."]) +``` + +This function supports updating modules from Puppet 3 style argument validation (using the stdlib `validate_*` functions) to Puppet 4 data types, without breaking functionality for those depending on Puppet 3 style validation. + +> Note: This function is compatible only with Puppet 4.4.0 (PE 2016.1) and later. + +##### For module users + +If you are running Puppet 4, the `validate_legacy` function can help you find and resolve deprecated Puppet 3 `validate_*` functions. These functions are deprecated as of stdlib version 4.13 and will be removed in a future version of stdlib. + +Puppet 4 allows improved defined type checking using [data types](https://docs.puppet.com/puppet/latest/reference/lang_data.html). Data types avoid some of the problems with Puppet 3's `validate_*` functions, which could sometimes be inconsistent. For example, [validate_numeric](#validate_numeric) unintentionally allowed not only numbers, but also arrays of numbers or strings that looked like numbers. + +If you run Puppet 4 and use modules with deprecated `validate_*` functions, you might encounter deprecation messages. The `validate_legacy` function makes these differences visible and makes it easier to move to the clearer Puppet 4 syntax. + +The deprecation messages you get can vary, depending on the modules and data that you use. These deprecation messages appear by default only in Puppet 4: + +* `Notice: Accepting previously invalid value for target type ''`: This message is informational only. You're using values that are allowed by the new type, but would have been invalid by the old validation function. +* `Warning: This method is deprecated, please use the stdlib validate_legacy function`: The module has not yet upgraded to `validate_legacy`. Use the [deprecation](#deprecation) options to silence warnings for now, or submit a fix with the module's developer. See the information [for module developers](#for-module-developers) below for how to fix the issue. +* `Warning: validate_legacy() expected value, got _`: Your code passes a value that was accepted by the Puppet 3-style validation, but will not be accepted by the next version of the module. Most often, you can fix this by removing quotes from numbers or booleans. +* `Error: Evaluation Error: Error while evaluating a Resource Statement, Evaluation Error: Error while evaluating a Function Call, validate_legacy() expected value, got `: Your code passes a value that is not acceptable to either the new or the old style validation. + +##### For module developers + +The `validate_legacy` function helps you move from Puppet 3 style validation to Puppet 4 validation without breaking functionality your module's users depend on. + +Moving to Puppet 4 type validation allows much better defined type checking using [data types](https://docs.puppet.com/puppet/latest/reference/lang_data.html). Many of Puppet 3's `validate_*` functions have surprising holes in their validation. For example, [validate_numeric](#validate_numeric) allows not only numbers, but also arrays of numbers or strings that look like numbers, without giving you any control over the specifics. + +For each parameter of your classes and defined types, choose a new Puppet 4 data type to use. In most cases, the new data type allows a different set of values than the original `validate_*` function. The situation then looks like this: + +| | `validate_` pass | `validate_` fail | +| ------------ | ---------------- | ---------------- | +| matches type | pass | pass, notice | +| fails type | pass, deprecated | fail | + +The code after the validation still has to handle all possible values for now, but users of your code can change their manifests to pass only values that match the new type. + +For each `validate_*` function in stdlib, there is a matching `Stdlib::Compat::*` type that allows the appropriate set of values. See the documentation in the `types/` directory in the stdlib source code for caveats. + +For example, given a class that should accept only numbers, like this: + +~~~ +class example($value) { + validate_numeric($value) +~~~ + +the resulting validation code looks like this: + +~~~ +class example( + Variant[Stdlib::Compat::Numeric, Numeric] $value +) { + validate_legacy(Numeric, 'validate_numeric', $value) +~~~ + +Here, the type of `$value` is defined as `Variant[Stdlib::Compat::Numeric, Numeric]`, which allows any `Numeric` (the new type), as well as all values previously accepted by `validate_numeric` (through `Stdlib::Compat::Numeric`). + +The call to `validate_legacy` takes care of triggering the correct log or fail message for you. It requires the new type, the previous validation function name, and all arguments to that function. + +If your module still supported Puppet 3, this is a breaking change. Update your `metadata.json` requirements section to indicate that your module no longer supports Puppet 3, and bump the major version of your module. With this change, all existing tests for your module should still pass. Create additional tests for the new possible values. + +As a breaking change, this is also a good time to call [`deprecation`](#deprecation) for any parameters you want to get rid of, or to add additional constraints on your parameters. + +After releasing this version, you can release another breaking change release where you remove all compat types and all calls to `validate_legacy`. At that time, you can also go through your code and remove any leftovers dealing with the previously possible values. + +Always note such changes in your CHANGELOG and README. + +#### `validate_numeric` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is a numeric value (or an array or string of numeric values). Aborts catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not a numeric (Integer or Float) or array of numerics, and if arg 2 and arg 3 are not convertable to a numeric. + + For passing and failing usage, see `validate_integer()`. It is all the same for validate_numeric, yet now floating point values are allowed, too. + +*Type*: statement. + +#### `validate_re` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Performs simple validation of a string against one or more regular expressions. The first argument of this function should be the string to +test, and the second argument should be a stringified regular expression (without the // delimiters) or an array of regular expressions. If none of the regular expressions match the string passed in, compilation aborts with a parse error. + + You can pass a third argument as the error message raised and shown to the user. + + The following strings validate against the regular expressions: + + ~~~ + validate_re('one', '^one$') + validate_re('one', [ '^one', '^two' ]) + ~~~ + + The following string fails to validate, causing compilation to abort: + + ~~~ + validate_re('one', [ '^two', '^three' ]) + ~~~ + + To set the error message: + + ~~~ + validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7') + ~~~ + + Note: Compilation terminates if the first argument is not a string. Always use quotes to force stringification: + + ~~~ + validate_re("${::operatingsystemmajrelease}", '^[57]$') + ~~~ + +*Type*: statement. + +#### `validate_slength` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is a string (or an array of strings), and is less than or equal to the length of the second argument. It fails if the first argument is not a string or array of strings, or if the second argument is not convertable to a number. Optionally, a minimum string length can be given as the third argument. + + The following values pass: + + ~~~ + validate_slength("discombobulate",17) + validate_slength(["discombobulate","moo"],17) + validate_slength(["discombobulate","moo"],17,3) + ~~~ + + The following values fail: + + ~~~ + validate_slength("discombobulate",1) + validate_slength(["discombobulate","thermometer"],5) + validate_slength(["discombobulate","moo"],17,10) + ~~~ + +*Type*: statement. + +#### `validate_string` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are string data structures. Aborts catalog compilation if any value fails this check. + +The following values pass: + + ~~~ + $my_string = "one two" + validate_string($my_string, 'three') + ~~~ + + The following values fail, causing compilation to abort: + + ~~~ + validate_string(true) + validate_string([ 'some', 'array' ]) + ~~~ + +*Note:* validate_string(undef) will not fail in this version of the functions API (incl. current and future parser). + +Instead, use: + + ~~~ + if $var == undef { + fail('...') + } + ~~~ + +*Type*: statement. + +#### `validate_x509_rsa_key_pair` + +Validates a PEM-formatted X.509 certificate and private key using OpenSSL. +Verifies that the certficate's signature was created from the supplied key. + +Fails catalog compilation if any value fails this check. + +Takes two arguments, the first argument must be a X.509 certificate and the +second must be an RSA private key: + + ~~~ + validate_x509_rsa_key_pair($cert, $key) + ~~~ + +*Type*: statement. + +#### `values` + +Returns the values of a given hash. For example, given `$hash = {'a'=1, 'b'=2, 'c'=3} values($hash)` returns [1,2,3]. + +*Type*: rvalue. + +#### `values_at` + +Finds values inside an array based on location. The first argument is the array you want to analyze, and the second argument can be a combination of: + + * A single numeric index + * A range in the form of 'start-stop' (eg. 4-9) + * An array combining the above + + For example, `values_at(['a','b','c'], 2)` returns ['c']; `values_at(['a','b','c'], ["0-1"])` returns ['a','b']; and `values_at(['a','b','c','d','e'], [0, "2-3"])` returns ['a','c','d']. + +*Type*: rvalue. + +#### `zip` + +Takes one element from first array given and merges corresponding elements from second array given. This generates a sequence of n-element arrays, where *n* is one more than the count of arguments. For example, `zip(['1','2','3'],['4','5','6'])` results in ["1", "4"], ["2", "5"], ["3", "6"]. *Type*: rvalue. + +## Limitations + +As of Puppet Enterprise 3.7, the stdlib module is no longer included in PE. PE users should install the most recent release of stdlib for compatibility with Puppet modules. + +### Version Compatibility + +Versions | Puppet 2.6 | Puppet 2.7 | Puppet 3.x | Puppet 4.x | +:---------------|:-----:|:---:|:---:|:----: +**stdlib 2.x** | **yes** | **yes** | no | no +**stdlib 3.x** | no | **yes** | **yes** | no +**stdlib 4.x** | no | **yes** | **yes** | no +**stdlib 4.6+** | no | **yes** | **yes** | **yes** +**stdlib 5.x** | no | no | **yes** | **yes** + +**stdlib 5.x**: When released, stdlib 5.x will drop support for Puppet 2.7.x. Please see [this discussion](https://github.com/puppetlabs/puppetlabs-stdlib/pull/176#issuecomment-30251414). + +## Development + +Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad hardware, software, and deployment configurations that Puppet is intended to serve. We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more information, see our [module contribution guide](https://docs.puppetlabs.com/forge/contributing.html). + +To report or research a bug with any part of this module, please go to +[http://tickets.puppetlabs.com/browse/MODULES](http://tickets.puppetlabs.com/browse/MODULES). + +## Contributors + +The list of contributors can be found at: [https://github.com/puppetlabs/puppetlabs-stdlib/graphs/contributors](https://github.com/puppetlabs/puppetlabs-stdlib/graphs/contributors). diff --git a/environments/prod/modules/stdlib/README_DEVELOPER.markdown b/environments/prod/modules/stdlib/README_DEVELOPER.markdown new file mode 100644 index 0000000..04349ed --- /dev/null +++ b/environments/prod/modules/stdlib/README_DEVELOPER.markdown @@ -0,0 +1,35 @@ +Puppet Specific Facts +===================== + +Facter is meant to stand alone and apart from Puppet. However, Facter often +runs inside Puppet and all custom facts included in the stdlib module will +almost always be evaluated in the context of Puppet and Facter working +together. + +Still, we don't want to write custom facts that blow up in the users face if +Puppet is not loaded in memory. This is often the case if the user runs +`facter` without also supplying the `--puppet` flag. + +Ah! But Jeff, the custom fact won't be in the `$LOAD_PATH` unless the user +supplies `--facter`! You might say... + +Not (always) true I say! If the user happens to have a CWD of +`/stdlib/lib` then the facts will automatically be evaluated and +blow up. + +In any event, it's pretty easy to write a fact that has no value if Puppet is +not loaded. Simply do it like this: + + Facter.add(:node_vardir) do + setcode do + # This will be nil if Puppet is not available. + Facter::Util::PuppetSettings.with_puppet do + Puppet[:vardir] + end + end + end + +The `Facter::Util::PuppetSettings.with_puppet` method accepts a block and +yields to it only if the Puppet library is loaded. If the Puppet library is +not loaded, then the method silently returns `nil` which Facter interprets as +an undefined fact value. The net effect is that the fact won't be set. diff --git a/environments/prod/modules/stdlib/README_SPECS.markdown b/environments/prod/modules/stdlib/README_SPECS.markdown new file mode 100644 index 0000000..917b631 --- /dev/null +++ b/environments/prod/modules/stdlib/README_SPECS.markdown @@ -0,0 +1,7 @@ +NOTE +==== + +This project's specs depend on puppet core, and thus they require the +`puppetlabs_spec_helper` project. For more information please see the README +in that project, which can be found here: [puppetlabs spec +helper](https://github.com/puppetlabs/puppetlabs_spec_helper) diff --git a/environments/prod/modules/stdlib/RELEASE_PROCESS.markdown b/environments/prod/modules/stdlib/RELEASE_PROCESS.markdown new file mode 100644 index 0000000..0f9328e --- /dev/null +++ b/environments/prod/modules/stdlib/RELEASE_PROCESS.markdown @@ -0,0 +1,24 @@ +# Contributing to this module # + + * Work in a topic branch + * Submit a github pull request + * Address any comments / feeback + * Merge into master using --no-ff + +# Releasing this module # + + * This module adheres to http://semver.org/ + * Look for API breaking changes using git diff vX.Y.Z..master + * If no API breaking changes, the minor version may be bumped. + * If there are API breaking changes, the major version must be bumped. + * If there are only small minor changes, the patch version may be bumped. + * Update the CHANGELOG + * Update the Modulefile + * Commit these changes with a message along the lines of "Update CHANGELOG and + Modulefile for release" + * Create an annotated tag with git tag -a vX.Y.Z -m 'version X.Y.Z' (NOTE the + leading v as per semver.org) + * Push the tag with git push origin --tags + * Build a new package with puppet-module or the rake build task if it exists + * Publish the new package to the forge + * Bonus points for an announcement to puppet-users. diff --git a/environments/prod/modules/stdlib/Rakefile b/environments/prod/modules/stdlib/Rakefile new file mode 100644 index 0000000..d12d854 --- /dev/null +++ b/environments/prod/modules/stdlib/Rakefile @@ -0,0 +1,37 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any? + +PuppetLint.configuration.fail_on_warnings = true +PuppetLint.configuration.send('relative') + +desc 'Generate pooler nodesets' +task :gen_nodeset do + require 'beaker-hostgenerator' + require 'securerandom' + require 'fileutils' + + agent_target = ENV['TEST_TARGET'] + if ! agent_target + STDERR.puts 'TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat-64default."' + agent_target = 'redhat-64default.' + end + + master_target = ENV['MASTER_TEST_TARGET'] + if ! master_target + STDERR.puts 'MASTER_TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat7-64mdcl"' + master_target = 'redhat7-64mdcl' + end + + targets = "#{master_target}-#{agent_target}" + cli = BeakerHostGenerator::CLI.new([targets]) + nodeset_dir = "tmp/nodesets" + nodeset = "#{nodeset_dir}/#{targets}-#{SecureRandom.uuid}.yaml" + FileUtils.mkdir_p(nodeset_dir) + File.open(nodeset, 'w') do |fh| + fh.print(cli.execute) + end + puts nodeset +end diff --git a/environments/prod/modules/stdlib/appveyor.yml b/environments/prod/modules/stdlib/appveyor.yml new file mode 100644 index 0000000..c87ed7c --- /dev/null +++ b/environments/prod/modules/stdlib/appveyor.yml @@ -0,0 +1,40 @@ +version: 1.1.x.{build} +skip_commits: + message: /^\(?doc\)?.*/ +clone_depth: 10 +init: +- SET +- 'mkdir C:\ProgramData\PuppetLabs\code && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0' +environment: + matrix: + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 21 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 21-x64 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 23 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 23-x64 + - PUPPET_GEM_VERSION: 4.2.3 + RUBY_VER: 21-x64 +matrix: + fast_finish: true +install: +- SET PATH=C:\Ruby%RUBY_VER%\bin;%PATH% +- bundle install --jobs 4 --retry 2 --without system_tests +- type Gemfile.lock +build: off +test_script: +- bundle exec puppet -V +- ruby -v +- bundle exec rake spec SPEC_OPTS='--format documentation' +notifications: +- provider: Email + to: + - nobody@nowhere.com + on_build_success: false + on_build_failure: false + on_build_status_changed: false diff --git a/environments/prod/modules/stdlib/checksums.json b/environments/prod/modules/stdlib/checksums.json new file mode 100644 index 0000000..2a972d4 --- /dev/null +++ b/environments/prod/modules/stdlib/checksums.json @@ -0,0 +1,508 @@ +{ + "CHANGELOG.md": "54254f2ff8453a9c05c420820f240f0e", + "CONTRIBUTING.md": "77d0440d7cd4206497f99065c60bed46", + "Gemfile": "064893aff1d6bf890eee483af3a79c9e", + "LICENSE": "3b83ef96387f14655fc854ddc3c6bd57", + "MAINTAINERS.md": "b40ec4f2c16145af6f723c2c34bb2ff9", + "NOTICE": "b2e552587e5969886fdd60481e8b0351", + "README.markdown": "c53e1b16e6eb345241addcb867e8c2a6", + "README_DEVELOPER.markdown": "220a8b28521b5c5d2ea87c4ddb511165", + "README_SPECS.markdown": "82bb4c6abbb711f40778b162ec0070c1", + "RELEASE_PROCESS.markdown": "94b92bc99ac4106ba1a74d5c04e520f9", + "Rakefile": "3851f083966b9bbd6d46e50dba5aa52a", + "appveyor.yml": "7e0ff52cc2abe3fa4f2d8d978bf18ad7", + "examples/file_line.pp": "48f7f162127beffea8f8e3e58db2355a", + "examples/has_interface_with.pp": "d69d520cf3ff4d0b495480afaca359ef", + "examples/has_ip_address.pp": "32f42575e49aa66f0f2398a70ae2a9f4", + "examples/has_ip_network.pp": "bfb8db068c58d77c4dd7ae9697536537", + "examples/init.pp": "b52fd907330ddbd9c3e070cf39f7b317", + "lib/facter/facter_dot_d.rb": "d71e93183a680ac78bc0389fd50470a0", + "lib/facter/package_provider.rb": "539766a71dfb2f65e94a7c91bf413fcf", + "lib/facter/pe_version.rb": "60d47406026c8201e51394227ddf780d", + "lib/facter/puppet_settings.rb": "9438c0839ae28dc52fffb8348ae5124f", + "lib/facter/root_home.rb": "35702ae0c7410ec4d2101113e2f697fa", + "lib/facter/service_provider.rb": "66cc42526eae631e306b397391f1f01c", + "lib/facter/util/puppet_settings.rb": "9f1d2593d0ae56bfca89d4b9266aeee1", + "lib/puppet/functions/deprecation.rb": "59b803eaa15b5a559040497bffc172ae", + "lib/puppet/functions/is_a.rb": "9dad7f8c9b75348cd97aca986ac0b29a", + "lib/puppet/functions/is_absolute_path.rb": "96b217f26d06dbac87a2c6a8cfd2d8c8", + "lib/puppet/functions/is_array.rb": "9292a646010d167417a1936b0b0c17b9", + "lib/puppet/functions/is_bool.rb": "73957f9efd75ed8a7ab867f9de6da117", + "lib/puppet/functions/is_float.rb": "af3bd6bb56878bac8cc4fe4f7564e4f9", + "lib/puppet/functions/is_ip_address.rb": "ee231c66c3e039778bf46702d89815a6", + "lib/puppet/functions/is_ipv4_address.rb": "900d33249906c4daa02aa79cac896548", + "lib/puppet/functions/is_ipv6_address.rb": "568fba9af6a83c8b536fafcda82eb448", + "lib/puppet/functions/is_numeric.rb": "33051800b886cc3b2119826b77c9821a", + "lib/puppet/functions/is_string.rb": "230e9eabc5c9e1d8d5fb7b3c6c12b300", + "lib/puppet/functions/length.rb": "03cf801867a919f801d290424fd2bb8d", + "lib/puppet/functions/type_of.rb": "bec00841aae556993c926ab298bc81cd", + "lib/puppet/functions/validate_absolute_path.rb": "54a610baa115c7505f1b35976b632a8e", + "lib/puppet/functions/validate_array.rb": "9052b0026da174636c276a2512cf5acc", + "lib/puppet/functions/validate_bool.rb": "fe979e402a5a3a19d013ce84b39ef06a", + "lib/puppet/functions/validate_hash.rb": "92ea8fc21bbbf6cc41f6bb9cfcaefce7", + "lib/puppet/functions/validate_integer.rb": "b0982b68a599262da2c6f2e032bc7713", + "lib/puppet/functions/validate_ip_address.rb": "65a12af9a2c2a9c70d820d04d19ec891", + "lib/puppet/functions/validate_ipv4_address.rb": "4a5039b99ac97cc0447faa343b9f7416", + "lib/puppet/functions/validate_ipv6_address.rb": "fbdf685432416505fed27d5647c26f9c", + "lib/puppet/functions/validate_legacy.rb": "d9f115f30c511cef536a821b94826094", + "lib/puppet/functions/validate_numeric.rb": "41b2cc7335395f617c2bfbeac8f579da", + "lib/puppet/functions/validate_re.rb": "42092f592ebf89b8a504b10c900230d8", + "lib/puppet/functions/validate_slength.rb": "3ae6fcc3f60032c923d06ab3e457b84e", + "lib/puppet/functions/validate_string.rb": "cc967a9d0ea156b2208d1760d7f6e1b2", + "lib/puppet/parser/functions/abs.rb": "a8e46ecf4fb378d314916599651cebe5", + "lib/puppet/parser/functions/any2array.rb": "a81e71d6b67a551d38770ba9a1948a75", + "lib/puppet/parser/functions/any2bool.rb": "8bbb74cb81ebdb164e1965415364080b", + "lib/puppet/parser/functions/assert_private.rb": "cb9cd79ed4e3d647a48467dfb6237d5c", + "lib/puppet/parser/functions/base64.rb": "0d121a32fbfe25b06a4e91b0259af91d", + "lib/puppet/parser/functions/basename.rb": "c61952b3f68fd86408c84fca2c3febb1", + "lib/puppet/parser/functions/bool2num.rb": "01b070b02611fda7e1491de0a8cd89fc", + "lib/puppet/parser/functions/bool2str.rb": "c00885242abf381ba482644ded70b688", + "lib/puppet/parser/functions/camelcase.rb": "bd5da699dfe1ec27ffe91b06187c5900", + "lib/puppet/parser/functions/capitalize.rb": "c94c50b0f147a22861c3b93c724e4343", + "lib/puppet/parser/functions/ceiling.rb": "7de4a96c8bb645fd5ce5c38438667228", + "lib/puppet/parser/functions/chomp.rb": "35a16c85bc59dc285baae631da1ae771", + "lib/puppet/parser/functions/chop.rb": "3beb80906fa3c759945a2664fe510b20", + "lib/puppet/parser/functions/clamp.rb": "6920e6c11b575fc65dadb21220c01a7a", + "lib/puppet/parser/functions/concat.rb": "d10e5428917895d4bb90c638b148b7fa", + "lib/puppet/parser/functions/convert_base.rb": "c3b3e59a49318af98dcb88aed7156629", + "lib/puppet/parser/functions/count.rb": "325bbd89a29e6ed0f31e0462d0a0d301", + "lib/puppet/parser/functions/deep_merge.rb": "d83696855578fb81b64b9e92b9c7cc7c", + "lib/puppet/parser/functions/defined_with_params.rb": "80eed5cedb1b5134e6a1cf44e86ae60a", + "lib/puppet/parser/functions/delete.rb": "7af1540fee4285d93b6b28fb490db70a", + "lib/puppet/parser/functions/delete_at.rb": "a2cba60ac86a676d4ed1ff846a917014", + "lib/puppet/parser/functions/delete_regex.rb": "91cd012ef5a636ffe541814ed232b909", + "lib/puppet/parser/functions/delete_undef_values.rb": "52beef9ee37f84ed2278a69ec4383125", + "lib/puppet/parser/functions/delete_values.rb": "b410f5618b4a6158a921acb7b2dc628d", + "lib/puppet/parser/functions/deprecation.rb": "4323210434d36e37977251f906a232b8", + "lib/puppet/parser/functions/difference.rb": "467e44aeaebd0ee0a51c8b89e3769f1f", + "lib/puppet/parser/functions/dig.rb": "1a2a8918f646c13dcb9876a22f9295ab", + "lib/puppet/parser/functions/dig44.rb": "3078b97ee941c261944857373d400ed6", + "lib/puppet/parser/functions/dirname.rb": "8a5579f9a9a13fd737ba65eccf8e6d5a", + "lib/puppet/parser/functions/dos2unix.rb": "be8359a5106a7832be4180e8207dd586", + "lib/puppet/parser/functions/downcase.rb": "02376505a00accd0ce6b148f869c9c86", + "lib/puppet/parser/functions/empty.rb": "6d7d2dde2971f6a40fa913f0af1c610c", + "lib/puppet/parser/functions/enclose_ipv6.rb": "ec39936a9a51dc5cb8ada6eeb829b239", + "lib/puppet/parser/functions/ensure_packages.rb": "0fca8d6dc6608dc16e316fcd39f692b7", + "lib/puppet/parser/functions/ensure_resource.rb": "de703fe63392b939fc2b4392975263de", + "lib/puppet/parser/functions/ensure_resources.rb": "c92d8b69d6354eda24aa3a13d88177b2", + "lib/puppet/parser/functions/flatten.rb": "6a27f5b922a24fec6e823f6f51c98090", + "lib/puppet/parser/functions/floor.rb": "07ba3b1662a14c60fe908b51231355ee", + "lib/puppet/parser/functions/fqdn_rand_string.rb": "9ac5f18e563094aee62ef7586267025d", + "lib/puppet/parser/functions/fqdn_rotate.rb": "2483d17df4e6cb25d92b4e8520f30957", + "lib/puppet/parser/functions/fqdn_uuid.rb": "d357e8837ba2ed8196e926f3697be521", + "lib/puppet/parser/functions/get_module_path.rb": "d4bf50da25c0b98d26b75354fa1bcc45", + "lib/puppet/parser/functions/getparam.rb": "440a9c381b9ad589504e2e9919e83c06", + "lib/puppet/parser/functions/getvar.rb": "0c8c5cef7e158e232a8cf6e42c10d0ff", + "lib/puppet/parser/functions/grep.rb": "c2f2dbdf79d16584579c3a7bc7b5902f", + "lib/puppet/parser/functions/has_interface_with.rb": "db65f5aac94e8fe105c7ca0cd1f66403", + "lib/puppet/parser/functions/has_ip_address.rb": "6ce9e6cdfb499b6ce86bf531848a18be", + "lib/puppet/parser/functions/has_ip_network.rb": "3cc7b923fd3cde5062ed9d0eaaa8155f", + "lib/puppet/parser/functions/has_key.rb": "7cd9728c38f0b0065f832dabd62b0e7e", + "lib/puppet/parser/functions/hash.rb": "c1ebb21cc5b984153a87c252a9854db2", + "lib/puppet/parser/functions/intersection.rb": "87469eb81ab00bbacfd744165beeaeec", + "lib/puppet/parser/functions/is_absolute_path.rb": "1ce9a6d1cd0a79087d73cb879ed04542", + "lib/puppet/parser/functions/is_array.rb": "bbce6768b688bc0f36978ecb0f60f7f4", + "lib/puppet/parser/functions/is_bool.rb": "b8800ff7a11b4e8c03616041e218225f", + "lib/puppet/parser/functions/is_domain_name.rb": "5c6106c070945a605c5adbd1852f0691", + "lib/puppet/parser/functions/is_email_address.rb": "1eb786779743e93a7bb9fe8087b38b8d", + "lib/puppet/parser/functions/is_float.rb": "6257620b98c5099293be7aa4088b88ce", + "lib/puppet/parser/functions/is_function_available.rb": "f13934d6b41561ef54d88cf0da86231b", + "lib/puppet/parser/functions/is_hash.rb": "6f1ccf659c41cb5dab4db9fa89f0b364", + "lib/puppet/parser/functions/is_integer.rb": "a4ae475a5a799c0cc46519e86aed2973", + "lib/puppet/parser/functions/is_ip_address.rb": "2eabe60c213df2e642a03ebfd4852497", + "lib/puppet/parser/functions/is_ipv4_address.rb": "ab854b47367921f657410fbe79054f0b", + "lib/puppet/parser/functions/is_ipv6_address.rb": "2a2be7adbdd5ca23c3e21f00d7d8bac7", + "lib/puppet/parser/functions/is_mac_address.rb": "1af27510d3b9b936384192a4e1dfbf8c", + "lib/puppet/parser/functions/is_numeric.rb": "bfd99f39e2506953ad00178db6fa07ea", + "lib/puppet/parser/functions/is_string.rb": "c1405dd293e9e3c738b83c4ef5aab1ef", + "lib/puppet/parser/functions/join.rb": "2ed4f56d296a4535da142e01b11a126d", + "lib/puppet/parser/functions/join_keys_to_values.rb": "71ad24ac1809739713c461141aedd082", + "lib/puppet/parser/functions/keys.rb": "c10485a3d6c53b6d57a891b9852898de", + "lib/puppet/parser/functions/load_module_metadata.rb": "805c5476a6e7083d133e167129885924", + "lib/puppet/parser/functions/loadjson.rb": "ffecf61ba2ec8011915d37009b1a273e", + "lib/puppet/parser/functions/loadyaml.rb": "668265f14327cba1d1400f2078b7b26b", + "lib/puppet/parser/functions/lstrip.rb": "e9cbd5c2233233940e7344478362fb9f", + "lib/puppet/parser/functions/max.rb": "529eb6ac3d88c03caa788167594bd487", + "lib/puppet/parser/functions/member.rb": "03d618926bbb9efd117950e6078c3878", + "lib/puppet/parser/functions/merge.rb": "f3dcc5c83440cdda2036cce69b61a14b", + "lib/puppet/parser/functions/min.rb": "8d829c8a55c9330ab02f962926221d4f", + "lib/puppet/parser/functions/num2bool.rb": "ddb603cf74f92e16a00f1447a4403429", + "lib/puppet/parser/functions/parsejson.rb": "15165fd3807d9f3d657697fa854d643d", + "lib/puppet/parser/functions/parseyaml.rb": "db54578d9d798ced75952217cf11b737", + "lib/puppet/parser/functions/pick.rb": "bf01f13bbfe2318e7f6a302ac7c4433f", + "lib/puppet/parser/functions/pick_default.rb": "ad3ea60262de408767786d37a54d45dc", + "lib/puppet/parser/functions/prefix.rb": "ece9341c5b232a25c58545718a54e92f", + "lib/puppet/parser/functions/private.rb": "1500a21d5cf19961c5b1d476df892d92", + "lib/puppet/parser/functions/pry.rb": "79a48e45196e4bbf0a3e658781513cf4", + "lib/puppet/parser/functions/pw_hash.rb": "3d540eb4c483cc9c111794ac2d24a4a7", + "lib/puppet/parser/functions/range.rb": "ab19430b6b9737cf56263eb65d80cba1", + "lib/puppet/parser/functions/regexpescape.rb": "6378fdd413237a37c322859877147a50", + "lib/puppet/parser/functions/reject.rb": "689f6a7c961a55fe9dcd240921f4c7f9", + "lib/puppet/parser/functions/reverse.rb": "209e7ef512963251571c515e2d0aee10", + "lib/puppet/parser/functions/rstrip.rb": "f3226709247741825f07d9ec20bd3887", + "lib/puppet/parser/functions/seeded_rand.rb": "2ad22e7613d894ae779c0c5b0e65dade", + "lib/puppet/parser/functions/shell_escape.rb": "13662933244e1af42ddca71ced4ac9e5", + "lib/puppet/parser/functions/shell_join.rb": "b99a23d5e62e2e1b98accde5c22e45c9", + "lib/puppet/parser/functions/shell_split.rb": "5317d71f3a7e293624b57aaca23f57d5", + "lib/puppet/parser/functions/shuffle.rb": "6c0555524ebc9ed5dd8295b836fb7163", + "lib/puppet/parser/functions/size.rb": "c171d46b87e377ebcc710ad5a5925a2b", + "lib/puppet/parser/functions/sort.rb": "0b7d1411decc4a92450828809fad0779", + "lib/puppet/parser/functions/squeeze.rb": "403ea46228a8e45e89c91168ed301fb6", + "lib/puppet/parser/functions/str2bool.rb": "ff82f179970a9429614bd37fa7a1ae2a", + "lib/puppet/parser/functions/str2saltedsha512.rb": "457ab12e4329494ae6276cfa4f20eb23", + "lib/puppet/parser/functions/strftime.rb": "8f15e2e3732b6d1d357a1fa1826800d4", + "lib/puppet/parser/functions/strip.rb": "da0ce253cb63a4863f15f9d145217db5", + "lib/puppet/parser/functions/suffix.rb": "1ce493098f17ea47e9435f994adbc6cd", + "lib/puppet/parser/functions/swapcase.rb": "48cad9bf415b5d79584c8e17ab7a06cc", + "lib/puppet/parser/functions/time.rb": "cd96d1f039f8875af083091e3637190b", + "lib/puppet/parser/functions/to_bytes.rb": "45248fd0bba8cfb24eac830fc0add17a", + "lib/puppet/parser/functions/try_get_value.rb": "09cd079ec5f0e5e2ac862c9ebe0f54fe", + "lib/puppet/parser/functions/type.rb": "4709f7ab8a8aad62d77a3c5d91a3aa08", + "lib/puppet/parser/functions/type3x.rb": "5d0391205bd1cfe8de985a44e8c3e8a9", + "lib/puppet/parser/functions/union.rb": "c02fca3fe102875ba020072b7edee532", + "lib/puppet/parser/functions/unique.rb": "0f79a96896149f7034cd85e31eed1e54", + "lib/puppet/parser/functions/unix2dos.rb": "b1f5087fcaca69d9395094204cce887a", + "lib/puppet/parser/functions/upcase.rb": "e875fc4f03adec1ff3b42d22f177441e", + "lib/puppet/parser/functions/uriescape.rb": "ba78def2cd0e60bdc4412df6c7b891ec", + "lib/puppet/parser/functions/validate_absolute_path.rb": "c4b12e101055380386a235cd2c92ad10", + "lib/puppet/parser/functions/validate_array.rb": "bee8327014714d4cd8dbb5c46611f594", + "lib/puppet/parser/functions/validate_augeas.rb": "61e828e7759ba3e1e563e1fdd68aa80f", + "lib/puppet/parser/functions/validate_bool.rb": "971700229c4b581f67f6fadc9019eb2c", + "lib/puppet/parser/functions/validate_cmd.rb": "7df12370db442eddddcf4dd7a5364b5e", + "lib/puppet/parser/functions/validate_email_address.rb": "a72656cbfeda622cdd5cfdafdf464095", + "lib/puppet/parser/functions/validate_hash.rb": "c2ae6299148ea200f8dfcf9204875182", + "lib/puppet/parser/functions/validate_integer.rb": "65aa35f7450794aaadb6ad2c2e114df7", + "lib/puppet/parser/functions/validate_ip_address.rb": "b23c3d5ce6839e32d0186411147a6a44", + "lib/puppet/parser/functions/validate_ipv4_address.rb": "593e8f832469cb6a48c5f16ee66c3b2d", + "lib/puppet/parser/functions/validate_ipv6_address.rb": "48d3733012818993eae662839183d139", + "lib/puppet/parser/functions/validate_numeric.rb": "0e36d370262b8bdef2f88f0a3cb5b30e", + "lib/puppet/parser/functions/validate_re.rb": "d5963c404e3ac1670553f306221c2655", + "lib/puppet/parser/functions/validate_slength.rb": "6cbcfe15378ca4a780bac786223aacac", + "lib/puppet/parser/functions/validate_string.rb": "8afa7b0dcfe17bfbbb5704ad54664cc2", + "lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb": "f17427dfc42128dc1df0ab04f37942e5", + "lib/puppet/parser/functions/values.rb": "c35978761496406cc3dafdccaa014236", + "lib/puppet/parser/functions/values_at.rb": "f7e6ad2a1126acd4fb5f7fcf9bfc2e2b", + "lib/puppet/parser/functions/zip.rb": "133f3d4c54640844e656e2e6e790318e", + "lib/puppet/provider/file_line/ruby.rb": "2bfa44964be201698186b94a60a5eb31", + "lib/puppet/type/anchor.rb": "bbd36bb49c3b554f8602d8d3df366c0c", + "lib/puppet/type/file_line.rb": "d65885f7f0afe49ee199d6bf873a5daf", + "manifests/init.pp": "9560a09f657d7eebbfdb920cefcc1d4f", + "manifests/stages.pp": "72eb4fa624474faf23b39e57cf1590bd", + "metadata.json": "c75558cd574027ce6326bc8f43d3cf07", + "spec/acceptance/abs_spec.rb": "5b60756b2b4da28314025f51989592d7", + "spec/acceptance/anchor_spec.rb": "0fdbe266d8b7c3dc172e338b978109ba", + "spec/acceptance/any2array_spec.rb": "444cfd34154539d896e5ef1488386372", + "spec/acceptance/base64_spec.rb": "6a1dd4144ba354f9bed14eb70f7d2cba", + "spec/acceptance/bool2num_spec.rb": "edc9cb8b89b95410326475d27ce74bd5", + "spec/acceptance/build_csv.rb": "f28ef587de764ade1513091c4906412c", + "spec/acceptance/capitalize_spec.rb": "4bb6471ec3a8a07260da8e249fae6ccd", + "spec/acceptance/ceiling_spec.rb": "46489eef94aa21bb1560383bb17d7d02", + "spec/acceptance/chomp_spec.rb": "8372fe8a875b1a599a89df1191f43bc0", + "spec/acceptance/chop_spec.rb": "d73d1c7c6a44df65677362bd2899bbc1", + "spec/acceptance/clamp_spec.rb": "8afaae07bf89e0af35474da489fd590f", + "spec/acceptance/concat_spec.rb": "bad5f40e0a501b436ec4264cd278290b", + "spec/acceptance/count_spec.rb": "f8abd435b077edd06ad76a96a2e610c0", + "spec/acceptance/deep_merge_spec.rb": "5f16342cb8c1b52d6a08717a2ef87117", + "spec/acceptance/defined_with_params_spec.rb": "8b47a7255b9d662009217f3cfd17ca03", + "spec/acceptance/delete_at_spec.rb": "b0c853ffe5fc121e051233d62f6e72b3", + "spec/acceptance/delete_spec.rb": "6ccb838a13037a56d1413aecb4fdac00", + "spec/acceptance/delete_undef_values_spec.rb": "7d849a978d3ecdaaf5e922acc6038ad8", + "spec/acceptance/delete_values_spec.rb": "b9f3dbffbb89fec32c6a5b2bd3b20901", + "spec/acceptance/deprecation_spec.rb": "cadc56a94cbc2f13965d698f581f582d", + "spec/acceptance/difference_spec.rb": "2f4e8ddd760f2d4dc9a4fb7b653e982f", + "spec/acceptance/dirname_spec.rb": "76e6b66474f070d8a89f3fe5bd187e85", + "spec/acceptance/downcase_spec.rb": "53f0f5b1867e0fd87ba59833c18b5ca2", + "spec/acceptance/empty_spec.rb": "be8a610f87790a321dbd4cc0542e2885", + "spec/acceptance/ensure_resource_spec.rb": "c0193d79f1db1985d313bedb93a4c7ae", + "spec/acceptance/flatten_spec.rb": "3ab1f9c9e761e5d758eeee7a2e32f295", + "spec/acceptance/floor_spec.rb": "4a20f6fc7142ef9357c8d18a408b5e52", + "spec/acceptance/fqdn_rand_string_spec.rb": "7744b45e282013c5fe637dbaf42f85b9", + "spec/acceptance/fqdn_rotate_spec.rb": "366816bb1d3102f64025d61e22eec79c", + "spec/acceptance/get_module_path_spec.rb": "d5d2258b2345359d1e51a638b97523cb", + "spec/acceptance/getparam_spec.rb": "d4037f1f546cf7a3e7dcabe787e637b5", + "spec/acceptance/getvar_spec.rb": "8451bced69bfd5599a552ddf49976d98", + "spec/acceptance/grep_spec.rb": "5e6650532658915d1004a6e6c0f1229d", + "spec/acceptance/has_interface_with_spec.rb": "e028fc7fc1023293ba32c48c9f46752a", + "spec/acceptance/has_ip_address_spec.rb": "f3443be46277f6084ca8e531562d8ac7", + "spec/acceptance/has_ip_network_spec.rb": "fa7a38450bef7e4408deb6b978d5a42f", + "spec/acceptance/has_key_spec.rb": "68e42fb69d15f27916e7943ab727afc6", + "spec/acceptance/hash_spec.rb": "1e8ff803d76d8f9e506c8a366a93ad90", + "spec/acceptance/intersection_spec.rb": "755c135f67811666b3c5152c5c5349c2", + "spec/acceptance/is_a_spec.rb": "4cdd8df78a6285de21d634c032e5a7c9", + "spec/acceptance/is_array_spec.rb": "1de1fb0b55759d4012033ae3ce0723c2", + "spec/acceptance/is_bool_spec.rb": "9984cd966d81c217f8ff4e252a4a66d1", + "spec/acceptance/is_domain_name_spec.rb": "948dc0c7372027e8f2bb9db85b8d66b2", + "spec/acceptance/is_float_spec.rb": "731d9b71e7b4ee5055339d08b2b5f707", + "spec/acceptance/is_function_available_spec.rb": "413964bfe8e18c34d8809590afcc4118", + "spec/acceptance/is_hash_spec.rb": "9095a364cba82a95a1edd0f92d2218c7", + "spec/acceptance/is_integer_spec.rb": "bc4e7192073531638be9f8a4d391a827", + "spec/acceptance/is_ip_address_spec.rb": "c2ee8738d62b4c9d42b3ef1a1b8573fc", + "spec/acceptance/is_ipv4_address_spec.rb": "09f6f3ad6a1c0a9e5dcfaab34a081ffc", + "spec/acceptance/is_ipv6_address_spec.rb": "d19a98176b2b7367e740d338df08c2ae", + "spec/acceptance/is_mac_address_spec.rb": "b53f033cc4943a19f597895df0b5210a", + "spec/acceptance/is_numeric_spec.rb": "95ecae5adde23e6b61cd090704c4572d", + "spec/acceptance/is_string_spec.rb": "d9ad89b30aff62aea4fb805c991c6abf", + "spec/acceptance/join_keys_to_values_spec.rb": "908f62013e1d797ddc0ba68c0fb7913b", + "spec/acceptance/join_spec.rb": "5609c646859140109af4ae1c63cc69d2", + "spec/acceptance/keys_spec.rb": "15edb2f0a5630dfadd9bd6c038e5cf95", + "spec/acceptance/loadjson_spec.rb": "7bf8eb220ed02b77e9956dade1acfcb3", + "spec/acceptance/loadyaml_spec.rb": "3810faad7411bc1d9c9274671bb68a9a", + "spec/acceptance/lstrip_spec.rb": "27a6e2c9e2afc5b3e0e98301798a80ad", + "spec/acceptance/max_spec.rb": "c39a2006dacdbd24a8acc5dc25f1a9f3", + "spec/acceptance/member_spec.rb": "10b9635c1daf636d66aabea6ff796e6a", + "spec/acceptance/merge_spec.rb": "a1502ebd4d069c5f818f7fd8da79e02d", + "spec/acceptance/min_spec.rb": "a6e73a33255bc202de93fd8fe679c510", + "spec/acceptance/nodesets/centos-7-x64.yml": "a713f3abd3657f0ae2878829badd23cd", + "spec/acceptance/nodesets/debian-8-x64.yml": "d2d2977900989f30086ad251a14a1f39", + "spec/acceptance/nodesets/default.yml": "b42da5a1ea0c964567ba7495574b8808", + "spec/acceptance/nodesets/docker/centos-7.yml": "8a3892807bdd62306ae4774f41ba11ae", + "spec/acceptance/nodesets/docker/debian-8.yml": "ac8e871d1068c96de5e85a89daaec6df", + "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "dc42ee922a96908d85b8f0f08203ce58", + "spec/acceptance/num2bool_spec.rb": "33ab633c5a6aa1e5e9c0cac9ec23c0d5", + "spec/acceptance/parsejson_spec.rb": "56353143de570034f59a87244bb9ff7b", + "spec/acceptance/parseyaml_spec.rb": "a564b2217350398d8a3f1b212d0c2ada", + "spec/acceptance/pick_default_spec.rb": "bc3512e0cc3bc34da5f44ae18573af5d", + "spec/acceptance/pick_spec.rb": "661747652e50bb522ad0c627fb8c3c58", + "spec/acceptance/prefix_spec.rb": "2b9df68e18e16144caa4440fd6acaa01", + "spec/acceptance/pw_hash_spec.rb": "979f0d209f1ef227a31d27a452fc2ed3", + "spec/acceptance/range_spec.rb": "f913a26f4bbe8b9432b6cb80a3b6d1ed", + "spec/acceptance/reject_spec.rb": "c63eb909288cf2210f72d3afa1e07b32", + "spec/acceptance/reverse_spec.rb": "eca705328f8e246a648e3f62434e662a", + "spec/acceptance/rstrip_spec.rb": "bad1e42f66d07b5d09ca1133b392bf6a", + "spec/acceptance/shuffle_spec.rb": "adc420bc01d83dde117b04baa4a5e2d8", + "spec/acceptance/size_spec.rb": "d923ec158f5c2275b9823dd118edd953", + "spec/acceptance/sort_spec.rb": "3ac4acd4233fbe624d926604d58acea6", + "spec/acceptance/squeeze_spec.rb": "2270c35766a5162cd732f0f761f0dc34", + "spec/acceptance/str2bool_spec.rb": "73ca9cbf9079ef52de10f90cb871e80c", + "spec/acceptance/str2saltedsha512_spec.rb": "1e89299c3802ba386589a5133a07dccc", + "spec/acceptance/strftime_spec.rb": "6cdfc95c02af2419eb14d173a23df678", + "spec/acceptance/strip_spec.rb": "711b6b73fce659ab83956c4454500e55", + "spec/acceptance/suffix_spec.rb": "c2ca0539e9e7b71f36ff470d3c18b303", + "spec/acceptance/swapcase_spec.rb": "30290d4378f19fa8bac4bc3347a36298", + "spec/acceptance/time_spec.rb": "74b6d4f21d46ca612a98efed9c48944c", + "spec/acceptance/to_bytes_spec.rb": "ff7d57f85cf1e211b0998235636edfd9", + "spec/acceptance/try_get_value_spec.rb": "8b62b969ece6f02c65f0cf3d365324b1", + "spec/acceptance/type_spec.rb": "909aa4d0c2463b04a3d3ff72cb0d4b42", + "spec/acceptance/union_spec.rb": "41b8705ac890ddd915a5f6c8b33620cd", + "spec/acceptance/unique_spec.rb": "0108f76cc12ecb0551a37a002a9bda85", + "spec/acceptance/upcase_spec.rb": "30721f352edd513a0cc36c94aa0fe760", + "spec/acceptance/uriescape_spec.rb": "69857543c97c35b23094cc6da0828055", + "spec/acceptance/validate_absolute_path_spec.rb": "b7f57e45a4d4d2a187bf9db99c481f98", + "spec/acceptance/validate_array_spec.rb": "ec0db1f08eb8d64fec1025b65b544365", + "spec/acceptance/validate_augeas_spec.rb": "744354dd9726be3624db10935ae2b13f", + "spec/acceptance/validate_bool_spec.rb": "950632bafc509e1965cd6a57646d87f6", + "spec/acceptance/validate_cmd_spec.rb": "1605939801210b1a53dc8873492551e0", + "spec/acceptance/validate_hash_spec.rb": "b79b1ef06301e1b97863281e3c419b0e", + "spec/acceptance/validate_ipv4_address_spec.rb": "547520f1ad1d55c52a257f4fb85ecc33", + "spec/acceptance/validate_ipv6_address_spec.rb": "feedad9317be19409e351011c7b852b9", + "spec/acceptance/validate_re_spec.rb": "bcc18798eccca866883b965baefc3cc9", + "spec/acceptance/validate_slength_spec.rb": "29311e9e910bba57a86be9a4893b43e5", + "spec/acceptance/validate_string_spec.rb": "cd36b419ebb8630618ec795781081101", + "spec/acceptance/values_at_spec.rb": "17e39ac5e094fb05522cb363e12186ab", + "spec/acceptance/values_spec.rb": "25d5abaffd9a108a52cdb156b5120e85", + "spec/acceptance/zip_spec.rb": "30ac38a83f8e92d642a20b53c5b66292", + "spec/aliases/absolute_path_spec.rb": "4326af270230f6d1aef6b1c68a31c53b", + "spec/aliases/absolutepath_spec.rb": "ab9a78b2aa5f6eb102ee648c5f88da61", + "spec/aliases/array_spec.rb": "09c3559333ec646387e4141ed37d671e", + "spec/aliases/bool_spec.rb": "c3f734df3cd334860bba95386b8e8ba6", + "spec/aliases/float_spec.rb": "dda76444d67dcf4e9ab4bf8d1a668b23", + "spec/aliases/hash_spec.rb": "8b0753fbb41a41655235f999a8cd8a17", + "spec/aliases/httpsurl_spec.rb": "49fa0c1da3f055228aaa7ab12471f126", + "spec/aliases/httpurl_spec.rb": "fe92b61091197895e9e0070adcaec1e9", + "spec/aliases/integer_spec.rb": "3d5c27c864e2385251f1c16a46ef717d", + "spec/aliases/ip_address.rb": "297c6577c0763f5121f9ca9c19718dfe", + "spec/aliases/ipv4_spec.rb": "8f3be58256671754f32287609c7a64ba", + "spec/aliases/ipv6_spec.rb": "68bc3bdf8f2dbb049f8f8daf93d78e82", + "spec/aliases/numeric_spec.rb": "a0031bb0085673ec7cc28d9c9b5a72e4", + "spec/aliases/string_spec.rb": "2b4661dbe97d7172b613edd50db91542", + "spec/aliases/unixpath_spec.rb": "1fac6f23e14c3589d26284a081c25c77", + "spec/aliases/windowspath_spec.rb": "4a3aec398b2c6ae86cb6d68ac5e8ab06", + "spec/fixtures/dscacheutil/root": "e1a7622f55f3d1be258c9a5b16b474be", + "spec/fixtures/lsuser/root": "2ed657fa157372a81634539bb1a56be8", + "spec/fixtures/test/manifests/absolute_path.pp": "79e9da69f05adc0e1ed41c20bf14e95f", + "spec/fixtures/test/manifests/absolutepath.pp": "8561fa141028cd38d0b8409cd69a1372", + "spec/fixtures/test/manifests/array.pp": "0e1d4146a9b2dd56063b2cab6f3d7571", + "spec/fixtures/test/manifests/bool.pp": "cf75826f36025bd73ff7f479acf7196d", + "spec/fixtures/test/manifests/ensure_resources.pp": "3265ae7af9382cfe7d9a13527265dcc9", + "spec/fixtures/test/manifests/float.pp": "132f9a63e60bba2fba68be83208c2e28", + "spec/fixtures/test/manifests/hash.pp": "84d647482ef0c7dc35348c13fb0ca23e", + "spec/fixtures/test/manifests/httpsurl.pp": "9047fc382d215ae55c065b0798a82a2d", + "spec/fixtures/test/manifests/httpurl.pp": "7165bf5607a4808f3da3f9baca911240", + "spec/fixtures/test/manifests/integer.pp": "3cb3807714ddcde25cd8eeca61581e3b", + "spec/fixtures/test/manifests/ip_address.pp": "78deea27fe1ff9e85c360eced4d2c256", + "spec/fixtures/test/manifests/ipv4.pp": "b4f0dfe6ae06d4f8b284d24e55f6a6a2", + "spec/fixtures/test/manifests/ipv6.pp": "4764f683b6013556e20d8669b21b5bdc", + "spec/fixtures/test/manifests/numeric.pp": "bffd26d7c0bb53628369f42c14b342cb", + "spec/fixtures/test/manifests/string.pp": "27b69c1eb36250bfbd474a0ef9ba847b", + "spec/fixtures/test/manifests/unixpath.pp": "fd062024efdf731e19026901d3fcc075", + "spec/fixtures/test/manifests/windowspath.pp": "7f942ec8f883a3ac0fcaf960ea24b7d8", + "spec/functions/abs_spec.rb": "7c0ebbd787b788d32b9bb21fe9061a2f", + "spec/functions/any2array_spec.rb": "22b7ce032a34d02b6858360025eb7644", + "spec/functions/any2bool_spec.rb": "d090dde9be26601a38980e8f77fa5970", + "spec/functions/assert_private_spec.rb": "a7e79ac7a1b28610a0e57e38e913c9fa", + "spec/functions/base64_spec.rb": "fa279d9a4f3060ee3f467e35b050d528", + "spec/functions/basename_spec.rb": "4aec9efc4fc6ed35de82d93b34dc0fab", + "spec/functions/bool2num_spec.rb": "6609136ff067b90d41cf27bf8838c3ea", + "spec/functions/bool2str_spec.rb": "52560617234393f960aedb496b49a628", + "spec/functions/camelcase_spec.rb": "4a13d3323535291fef3f40a96710acdb", + "spec/functions/capitalize_spec.rb": "31a8d497b274653d5ede70a0187d4053", + "spec/functions/ceiling_spec.rb": "47bd74569f8979d9195df06a863de93b", + "spec/functions/chomp_spec.rb": "8be3a83a34945dcaa6e8e193cf2751e4", + "spec/functions/chop_spec.rb": "8e1f661e93dd87b8bbeebe293fe16612", + "spec/functions/clamp_spec.rb": "65dab1e9aceb2cb4e41ef9c0c65a7ba3", + "spec/functions/concat_spec.rb": "4161a254ff4c21497e61a62ce84dafe8", + "spec/functions/convert_base_spec.rb": "f031e84b18cb010bc6233be3e4bcff83", + "spec/functions/count_spec.rb": "14ab4e4b10fe33d69d0662357cd4bf8b", + "spec/functions/deep_merge_spec.rb": "5a4e826aaa0a0c0e4247b72e03bdfbd1", + "spec/functions/defined_with_params_spec.rb": "9a0af61ab4bfc380ef4707005344fac2", + "spec/functions/delete_at_spec.rb": "188a3143799bd5cf121982883c7dc1c8", + "spec/functions/delete_regex_spec.rb": "c049a3926aded4b6a804f1c824c57d2f", + "spec/functions/delete_spec.rb": "d7c589404fdc3a6b65da7ecf1f7e0b02", + "spec/functions/delete_undef_values_spec.rb": "a2c138145333e1873ee4bf23d091f86a", + "spec/functions/delete_values_spec.rb": "e20b68a589002f1e2b52873d3dbcfb49", + "spec/functions/deprecation_spec.rb": "f5d7355b687eaaa15c20fdb214671c2a", + "spec/functions/difference_spec.rb": "66ba399c0bace1a131ecdb365e0afd4e", + "spec/functions/dig44_spec.rb": "89631bb7282f7d3c9b75d79f48446c65", + "spec/functions/dig_spec.rb": "61708160da38852c3c78bc8563e0fcb2", + "spec/functions/dirname_spec.rb": "e80baeab6a166284f5069263fb5e37b7", + "spec/functions/dos2unix_spec.rb": "d0cfe5fe19d2bf33320e6f47789826c6", + "spec/functions/downcase_spec.rb": "e2c24d41c6fb840f7b66c5205c942780", + "spec/functions/empty_spec.rb": "e6d06c193869ce8c97d3e67d5c5c5b4f", + "spec/functions/ensure_packages_spec.rb": "81f281f4c4f2146328b65b49ad4e846b", + "spec/functions/ensure_resource_spec.rb": "6461ab093bc10308e1c81b01482c409f", + "spec/functions/flatten_spec.rb": "5d59251fa8da0e4c8a376a513a25bc7a", + "spec/functions/floor_spec.rb": "7d110b1f994432e1c6c7c004a3dedbe4", + "spec/functions/fqdn_rand_string_spec.rb": "950a33e5ad8056fbfe564ec7e3b9a583", + "spec/functions/fqdn_rotate_spec.rb": "83f00ce0c48dec632e1aaa8192bb51cf", + "spec/functions/fqdn_uuid_spec.rb": "1b2980fcbecc4b51bdc6de01e9b001b7", + "spec/functions/get_module_path_spec.rb": "ef22bd80ce5dedb069830d5aa10572c1", + "spec/functions/getparam_spec.rb": "359d7c9349ec0e5666bb38e2d9224621", + "spec/functions/getvar_spec.rb": "3c7884e012bdb596df3d0640533dab96", + "spec/functions/grep_spec.rb": "0e917284860c5f96fdf56301012ba199", + "spec/functions/has_interface_with_spec.rb": "473c000e461c3497f8461eb17cf73430", + "spec/functions/has_ip_address_spec.rb": "7b36b993ea32757e74be9909906bd165", + "spec/functions/has_ip_network_spec.rb": "7086a27623ba91c5ff069397a32172c4", + "spec/functions/has_key_spec.rb": "eaa046db8632d6f76893e4b9df7b3e31", + "spec/functions/hash_spec.rb": "53a059e13781fac6b4f8eb660655d58f", + "spec/functions/intersection_spec.rb": "e906cb8ceb39b644b4b338adcd4dda26", + "spec/functions/is_a_spec.rb": "7d6322426d629e5302c52455d9c4cd55", + "spec/functions/is_array_spec.rb": "26d7eab025f333d2c4a8baf89ef4e36e", + "spec/functions/is_bool_spec.rb": "40569a4cdb50af0cc21ad749ac18e6ef", + "spec/functions/is_domain_name_spec.rb": "4ea3825d5b77a5d75eab8228dcc738f9", + "spec/functions/is_email_address_spec.rb": "699c4573dfd00db8993a18afc0eaf875", + "spec/functions/is_float_spec.rb": "8aedd9e622cd1b861d99523532dc4fc1", + "spec/functions/is_function_available.rb": "f8ab234d536532c3629ff6a5068e7877", + "spec/functions/is_hash_spec.rb": "11563529f0f1f821769edb3131277100", + "spec/functions/is_integer_spec.rb": "21a139f9a87b1561bfec60de26c435a2", + "spec/functions/is_ip_address_spec.rb": "04a15328803bb47ae431eff63aa555df", + "spec/functions/is_ipv4_address_spec.rb": "339b8c79925bb70c0de19468d58ab64a", + "spec/functions/is_ipv6_address_spec.rb": "9743a0f2bfda70ace128d2f476a208b9", + "spec/functions/is_mac_address_spec.rb": "532b8c4c63b37849e7fb9122d163ae3a", + "spec/functions/is_numeric_spec.rb": "cb846f44e8f6d79161686d7b3729967c", + "spec/functions/is_string_spec.rb": "56f34694781dfcf774e43bca06057fec", + "spec/functions/join_keys_to_values_spec.rb": "f99dff6550fdb7c245ed4dcaca0a16a3", + "spec/functions/join_spec.rb": "e190fbce526b3f4f7bb8aac029338ece", + "spec/functions/keys_spec.rb": "fba0bfa928b5684d5c918d6c06edd06d", + "spec/functions/length.rb": "c07a267ccccc4b4edbbfad155dc21475", + "spec/functions/load_module_metadata_spec.rb": "431fbe84d627a5c4398f432caaec96f8", + "spec/functions/loadjson_spec.rb": "76cbe175c90a596892b799d8e4d599e5", + "spec/functions/loadyaml_spec.rb": "60e283bc07adc67bc2c934424bf01b8d", + "spec/functions/lstrip_spec.rb": "892229ce8657490c72fc860fb0dda1b6", + "spec/functions/max_spec.rb": "47de8d59070d8d51b2184731f5d1aa43", + "spec/functions/member_spec.rb": "6a988e6368c1ce090b47fe41efbc041c", + "spec/functions/merge_spec.rb": "56297527d192640bbe82c7ccf1e39815", + "spec/functions/min_spec.rb": "8b38e2a989912406cd2c57dcd3a460c4", + "spec/functions/num2bool_spec.rb": "7c4fd30e41a11b1bd0d9e5233340f16b", + "spec/functions/parsejson_spec.rb": "958db1dde2f2244f458c399e0c3231e0", + "spec/functions/parseyaml_spec.rb": "19a2add90ca08d5a17c4e3aff89fe3c0", + "spec/functions/pick_default_spec.rb": "ab9ef2e9111cfd2709313e0e3c3befa2", + "spec/functions/pick_spec.rb": "786044a71e3bd673212949c12f37b10f", + "spec/functions/prefix_spec.rb": "2402d2ad5108050bb5c20b3ea58b64be", + "spec/functions/private_spec.rb": "f404771c4590a0cd7ce61ddff8f3eb7b", + "spec/functions/pw_hash_spec.rb": "640609cc73b4c8bbcdfc88c3e9797664", + "spec/functions/range_spec.rb": "d3af56cc8a137059423f8738e343ccff", + "spec/functions/regexpescape_spec.rb": "ce027e1b99674f39de8ddffcfed8396e", + "spec/functions/reject_spec.rb": "8ecbca64a9817ebef76be96dcf6cdeeb", + "spec/functions/reverse_spec.rb": "e40754fd34e96ac206308958504ae315", + "spec/functions/rstrip_spec.rb": "748f4587c12b5a590b985f111e282a8d", + "spec/functions/seeded_rand_spec.rb": "eb56445d10962d81ed935b53826da349", + "spec/functions/shell_escape_spec.rb": "bf108573c041f0c228f6d47d367d0aa3", + "spec/functions/shell_join_spec.rb": "92f31bd98d6e8e30e5f758f50bcb8a75", + "spec/functions/shell_split_spec.rb": "430f58733cc7855e56bfc918971b3fc1", + "spec/functions/shuffle_spec.rb": "be5d43184f2ca42db4f452085a4730fd", + "spec/functions/size_spec.rb": "f5e35878331bb12d9639522e38cc81c8", + "spec/functions/sort_spec.rb": "d9533dd37c6263b92895f7eba8485248", + "spec/functions/squeeze_spec.rb": "ffcf3b44cbdd45e14eb1173d2b7d1d99", + "spec/functions/str2bool_spec.rb": "607b25fb0badd0da5acb86c63437c8be", + "spec/functions/str2saltedsha512_spec.rb": "07586b0026757cd39229c12c7221808b", + "spec/functions/strftime_spec.rb": "f1a34fc930940213abfba3095d5d8065", + "spec/functions/strip_spec.rb": "023132b8bd0820da254f74c6fe31bb80", + "spec/functions/suffix_spec.rb": "dfe26a61bf0185291b6ae868e9d0de20", + "spec/functions/swapcase_spec.rb": "90bace1b004aa63d46eb6481c6dce2b1", + "spec/functions/time_spec.rb": "6dc8f5b42cf89345d2de424bfe19be90", + "spec/functions/to_bytes_spec.rb": "b771f8490d922de46a519e407d358139", + "spec/functions/try_get_value_spec.rb": "b917b899f5d29764dd4b1b07e07ec6ce", + "spec/functions/type3x_spec.rb": "eed4ce3a2bc92d14effedefef9690654", + "spec/functions/type_of_spec.rb": "83755d9390b9c00e086a007edff7fd9b", + "spec/functions/type_spec.rb": "7a61b4af7d3d83be590d783a7e5e80f8", + "spec/functions/union_spec.rb": "a290a38e443b856a476d14d4b1dc72fb", + "spec/functions/unique_spec.rb": "97fbcc73a0e0d82e23c723837627c712", + "spec/functions/unix2dos_spec.rb": "628c8a0c608d1fa0dd09bd1b5af97c1f", + "spec/functions/upcase_spec.rb": "4f0461a20c03d618f0c18da39bebcf65", + "spec/functions/uriescape_spec.rb": "1458afbe7e7e11dcaad8d295a7f2be59", + "spec/functions/validate_absolute_path_spec.rb": "5adbf0a0ac8b23e86195deaf3e4bd371", + "spec/functions/validate_array_spec.rb": "3aa76f3731754c637acc94b59c79efb0", + "spec/functions/validate_augeas_spec.rb": "d598e89a23912be9f24d39b809f30b47", + "spec/functions/validate_bool_spec.rb": "d1b43cd294d0588ac6d67d5d3c77a42a", + "spec/functions/validate_cmd_spec.rb": "34a4e623ab0ce7d4c218a837f896c1ea", + "spec/functions/validate_email_address_spec.rb": "1296f9934d102258091379ee07f9a2a8", + "spec/functions/validate_hash_spec.rb": "0b3ac3f7262f13ef14e3df937c8d104c", + "spec/functions/validate_integer_spec.rb": "ec55ab34e3ec4f09722349cd6aac77a6", + "spec/functions/validate_ip_address_spec.rb": "ba8c92e30f1af9e6839a2901e002c3f3", + "spec/functions/validate_ipv4_address_spec.rb": "d5d956651629f95ea531ac03b71e247b", + "spec/functions/validate_ipv6_address_spec.rb": "7ee0d6097666558d8627de424c26ef75", + "spec/functions/validate_legacy_spec.rb": "a1a3af70e71d0a6b848d75c518949b3b", + "spec/functions/validate_numeric_spec.rb": "c2f01b9c9aeae75683563ef7d3951bbb", + "spec/functions/validate_re_spec.rb": "27bcf99cd815b755548cd3979753b2f3", + "spec/functions/validate_slength_spec.rb": "c292023b2e61298499ff1f791fc03005", + "spec/functions/validate_string_spec.rb": "bc6735359ada0b3ccf69f515231e3808", + "spec/functions/validate_x509_rsa_key_pair_spec.rb": "24810bdb8ac473ae1168f1b2c5708170", + "spec/functions/values_at_spec.rb": "866ba43a4b606f6e9e72ac91c07408bd", + "spec/functions/values_spec.rb": "6b8f39665cb8fdb03658d838e50b55b0", + "spec/functions/zip_spec.rb": "986defd43a001ff28fe7c90b0366812e", + "spec/monkey_patches/alias_should_to_must.rb": "b19ee31563afb91a72f9869f9d7362ff", + "spec/monkey_patches/publicize_methods.rb": "c690e444b77c871375d321e413e28ca1", + "spec/spec_helper.rb": "b2db3bc02b4ac2fd5142a6621c641b07", + "spec/spec_helper_acceptance.rb": "cd589056b52031da19d9c31ed86ea922", + "spec/spec_helper_local.rb": "1e3ad3c06a11cbcb813dc1d31973cbda", + "spec/support/shared_data.rb": "9758f8ba2958965d287387a2513dac73", + "spec/unit/ensure_resources_spec.rb": "30cad259a3cc6c73f90209804183c818", + "spec/unit/facter/facter_dot_d_spec.rb": "420339a544851f2c7ee6fa4c651bdce8", + "spec/unit/facter/package_provider_spec.rb": "3a6ba799822fbcabc9adab5880260b7a", + "spec/unit/facter/pe_version_spec.rb": "d0fa6c0d5b01a4b9fd36ed4168635e9f", + "spec/unit/facter/root_home_spec.rb": "036c160d5543f4f3e80a300a3a170b77", + "spec/unit/facter/service_provider_spec.rb": "a97efb411817a44c511cd6cd79d9af8c", + "spec/unit/facter/util/puppet_settings_spec.rb": "6f9df9b10a1b39245ecdf002616a4122", + "spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb": "0145a78254ea716e5e7600d9464318a8", + "spec/unit/puppet/parser/functions/is_absolute_path_spec.rb": "52f8d3b9011fb1fd8a2a429fe8b2ae08", + "spec/unit/puppet/provider/file_line/ruby_spec.rb": "d269e18953fc297786a8393b20842abc", + "spec/unit/puppet/type/anchor_spec.rb": "06a669dffa44d716bf19b4e7f5f1d75d", + "spec/unit/puppet/type/file_line_spec.rb": "83e11166de4abe69cdf0dc7450e79778", + "types/absolutepath.pp": "3ae6f48dd95835df87c5dacea13c88d2", + "types/compat/absolute_path.pp": "6d0e102d1098d85d97c8f13051cb2c10", + "types/compat/array.pp": "43c41022dd743d55c8e9e82ce3ed741c", + "types/compat/bool.pp": "6e0cd7b2a7e1b9efaa0e273937211e58", + "types/compat/float.pp": "fd134a0e0e1abd420cec2107780a7afa", + "types/compat/hash.pp": "3535d98f0f3eaec44d443190f258e9ba", + "types/compat/integer.pp": "e85a20e42b3afaf5ec0718a8d9f9c5e3", + "types/compat/ip_address.pp": "d42c7435de30a469a71b69c45d642da7", + "types/compat/ipv4.pp": "c84eb3764f45b256a09be68afe61b749", + "types/compat/ipv6.pp": "d3c0fada5425d576aeca14ddbee78653", + "types/compat/numeric.pp": "f864539d78e95dabf0bab361b4ccc042", + "types/compat/re.pp": "70b05d4697e61f9af0c212ba063f395d", + "types/compat/string.pp": "b9a179664c85e121aa0277021627d126", + "types/httpsurl.pp": "108225d0273e17d541b239882287ed15", + "types/httpurl.pp": "4ba64825b11af75997c59b5acf07c62d", + "types/unixpath.pp": "5fc80523e7f474dd84056c44fafd9544", + "types/windowspath.pp": "4c1eda331ecb43de98c29ed22d0ad448" +} \ No newline at end of file diff --git a/environments/prod/modules/stdlib/examples/file_line.pp b/environments/prod/modules/stdlib/examples/file_line.pp new file mode 100644 index 0000000..85b1325 --- /dev/null +++ b/environments/prod/modules/stdlib/examples/file_line.pp @@ -0,0 +1,9 @@ +# This is a simple smoke test +# of the file_line resource type. +file { '/tmp/dansfile': + ensure => file, +} -> +file_line { 'dans_line': + line => 'dan is awesome', + path => '/tmp/dansfile', +} diff --git a/environments/prod/modules/stdlib/examples/has_interface_with.pp b/environments/prod/modules/stdlib/examples/has_interface_with.pp new file mode 100644 index 0000000..a578dd2 --- /dev/null +++ b/environments/prod/modules/stdlib/examples/has_interface_with.pp @@ -0,0 +1,9 @@ +include ::stdlib +info('has_interface_with(\'lo\'):', has_interface_with('lo')) +info('has_interface_with(\'loX\'):', has_interface_with('loX')) +info('has_interface_with(\'ipaddress\', \'127.0.0.1\'):', has_interface_with('ipaddress', '127.0.0.1')) +info('has_interface_with(\'ipaddress\', \'127.0.0.100\'):', has_interface_with('ipaddress', '127.0.0.100')) +info('has_interface_with(\'network\', \'127.0.0.0\'):', has_interface_with('network', '127.0.0.0')) +info('has_interface_with(\'network\', \'128.0.0.0\'):', has_interface_with('network', '128.0.0.0')) +info('has_interface_with(\'netmask\', \'255.0.0.0\'):', has_interface_with('netmask', '255.0.0.0')) +info('has_interface_with(\'netmask\', \'256.0.0.0\'):', has_interface_with('netmask', '256.0.0.0')) diff --git a/environments/prod/modules/stdlib/examples/has_ip_address.pp b/environments/prod/modules/stdlib/examples/has_ip_address.pp new file mode 100644 index 0000000..594143d --- /dev/null +++ b/environments/prod/modules/stdlib/examples/has_ip_address.pp @@ -0,0 +1,3 @@ +include ::stdlib +info('has_ip_address(\'192.168.1.256\'):', has_ip_address('192.168.1.256')) +info('has_ip_address(\'127.0.0.1\'):', has_ip_address('127.0.0.1')) diff --git a/environments/prod/modules/stdlib/examples/has_ip_network.pp b/environments/prod/modules/stdlib/examples/has_ip_network.pp new file mode 100644 index 0000000..1f1130d --- /dev/null +++ b/environments/prod/modules/stdlib/examples/has_ip_network.pp @@ -0,0 +1,3 @@ +include ::stdlib +info('has_ip_network(\'127.0.0.0\'):', has_ip_network('127.0.0.0')) +info('has_ip_network(\'128.0.0.0\'):', has_ip_network('128.0.0.0')) diff --git a/environments/prod/modules/stdlib/examples/init.pp b/environments/prod/modules/stdlib/examples/init.pp new file mode 100644 index 0000000..ad27972 --- /dev/null +++ b/environments/prod/modules/stdlib/examples/init.pp @@ -0,0 +1 @@ +include ::stdlib diff --git a/environments/prod/modules/stdlib/lib/facter/facter_dot_d.rb b/environments/prod/modules/stdlib/lib/facter/facter_dot_d.rb new file mode 100644 index 0000000..5c5fb1f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/facter_dot_d.rb @@ -0,0 +1,202 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d +# and /etc/puppetlabs/facter/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. +# +# In the case of scripts you can also create a file that +# contains a cache TTL. For foo.sh store the ttl as just +# a number in foo.sh.ttl +# +# The cache is stored in $libdir/facts_dot_d.cache as a mode +# 600 file and will have the end result of not calling your +# fact scripts more often than is needed + +class Facter::Util::DotD + require 'yaml' + + def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache")) + @dir = dir + @cache_file = cache_file + @cache = nil + @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml} + end + + def entries + Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) } + rescue + [] + end + + def fact_type(file) + extension = File.extname(file) + + type = @types[extension] || :unknown + + type = :script if type == :unknown && File.executable?(file) + + return type + end + + def txt_parser(file) + File.readlines(file).each do |line| + if line =~ /^([^=]+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") + end + + def json_parser(file) + begin + require 'json' + rescue LoadError + retry if require 'rubygems' + raise + end + + JSON.load(File.read(file)).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}") + end + + def yaml_parser(file) + require 'yaml' + + YAML.load_file(file).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}") + end + + def script_parser(file) + result = cache_lookup(file) + ttl = cache_time(file) + + unless result + result = Facter::Util::Resolution.exec(file) + + if ttl > 0 + Facter.debug("Updating cache for #{file}") + cache_store(file, result) + cache_save! + end + else + Facter.debug("Using cached data for #{file}") + end + + result.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + + def cache_save! + cache = load_cache + File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) } + rescue + end + + def cache_store(file, data) + load_cache + + @cache[file] = {:data => data, :stored => Time.now.to_i} + rescue + end + + def cache_lookup(file) + cache = load_cache + + return nil if cache.empty? + + ttl = cache_time(file) + + if cache[file] + now = Time.now.to_i + + return cache[file][:data] if ttl == -1 + return cache[file][:data] if (now - cache[file][:stored]) <= ttl + return nil + else + return nil + end + rescue + return nil + end + + def cache_time(file) + meta = file + ".ttl" + + return File.read(meta).chomp.to_i + rescue + return 0 + end + + def load_cache + unless @cache + if File.exist?(@cache_file) + @cache = YAML.load_file(@cache_file) + else + @cache = {} + end + end + + return @cache + rescue + @cache = {} + return @cache + end + + def create + entries.each do |fact| + type = fact_type(fact) + parser = "#{type}_parser" + + if respond_to?("#{type}_parser") + Facter.debug("Parsing #{fact} using #{parser}") + + send(parser, fact) + end + end + end +end + + +mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/) +if mdata + (major, minor, patch) = mdata.captures.map { |v| v.to_i } + if major < 2 + # Facter 1.7 introduced external facts support directly + unless major == 1 and minor > 6 + Facter::Util::DotD.new("/etc/facter/facts.d").create + Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + + # Windows has a different configuration directory that defaults to a vendor + # specific sub directory of the %COMMON_APPDATA% directory. + if Dir.const_defined? 'COMMON_APPDATA' then + windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') + Facter::Util::DotD.new(windows_facts_dot_d).create + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/package_provider.rb b/environments/prod/modules/stdlib/lib/facter/package_provider.rb new file mode 100644 index 0000000..3a9117f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/package_provider.rb @@ -0,0 +1,21 @@ +# Fact: package_provider +# +# Purpose: Returns the default provider Puppet will choose to manage packages +# on this system +# +# Resolution: Instantiates a dummy package resource and return the provider +# +# Caveats: +# +require 'puppet/type' +require 'puppet/type/package' + +Facter.add(:package_provider) do + setcode do + if defined? Gem and Gem::Version.new(Facter.value(:puppetversion).split(' ')[0]) >= Gem::Version.new('3.6') + Puppet::Type.type(:package).newpackage(:name => 'dummy', :allow_virtual => 'true')[:provider].to_s + else + Puppet::Type.type(:package).newpackage(:name => 'dummy')[:provider].to_s + end + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/pe_version.rb b/environments/prod/modules/stdlib/lib/facter/pe_version.rb new file mode 100644 index 0000000..c9f2181 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/pe_version.rb @@ -0,0 +1,58 @@ +# Fact: is_pe, pe_version, pe_major_version, pe_minor_version, pe_patch_version +# +# Purpose: Return various facts about the PE state of the system +# +# Resolution: Uses a regex match against puppetversion to determine whether the +# machine has Puppet Enterprise installed, and what version (overall, major, +# minor, patch) is installed. +# +# Caveats: +# +Facter.add("pe_version") do + setcode do + puppet_ver = Facter.value("puppetversion") + if puppet_ver != nil + pe_ver = puppet_ver.match(/Puppet Enterprise (\d+\.\d+\.\d+)/) + pe_ver[1] if pe_ver + else + nil + end + end +end + +Facter.add("is_pe") do + setcode do + if Facter.value(:pe_version).to_s.empty? then + false + else + true + end + end +end + +Facter.add("pe_major_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[0] + end + end +end + +Facter.add("pe_minor_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[1] + end + end +end + +Facter.add("pe_patch_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[2] + end + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/puppet_settings.rb b/environments/prod/modules/stdlib/lib/facter/puppet_settings.rb new file mode 100644 index 0000000..eac9e97 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/puppet_settings.rb @@ -0,0 +1,43 @@ +# These facter facts return the value of the Puppet vardir and environment path +# settings for the node running puppet or puppet agent. The intent is to +# enable Puppet modules to automatically have insight into a place where they +# can place variable data, or for modules running on the puppet master to know +# where environments are stored. +# +# The values should be directly usable in a File resource path attribute. +# +begin + require 'facter/util/puppet_settings' +rescue LoadError => e + # puppet apply does not add module lib directories to the $LOAD_PATH (See + # #4248). It should (in the future) but for the time being we need to be + # defensive which is what this rescue block is doing. + rb_file = File.join(File.dirname(__FILE__), 'util', 'puppet_settings.rb') + load rb_file if File.exists?(rb_file) or raise e +end + + +# These will be nil if Puppet is not available. +Facter.add(:puppet_vardir) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:vardir] + end + end +end + +Facter.add(:puppet_environmentpath) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:environmentpath] + end + end +end + +Facter.add(:puppet_server) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:server] + end + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/root_home.rb b/environments/prod/modules/stdlib/lib/facter/root_home.rb new file mode 100644 index 0000000..87c7657 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/root_home.rb @@ -0,0 +1,45 @@ +# A facter fact to determine the root home directory. +# This varies on PE supported platforms and may be +# reconfigured by the end user. + +module Facter::Util::RootHome + class << self + def get_root_home + root_ent = Facter::Util::Resolution.exec("getent passwd root") + # The home directory is the sixth element in the passwd entry + # If the platform doesn't have getent, root_ent will be nil and we should + # return it straight away. + root_ent && root_ent.split(":")[5] + end + end +end + +Facter.add(:root_home) do + setcode { Facter::Util::RootHome.get_root_home } +end + +Facter.add(:root_home) do + confine :kernel => :darwin + setcode do + str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root") + hash = {} + str.split("\n").each do |pair| + key,value = pair.split(/:/) + hash[key] = value + end + hash['dir'].strip + end +end + +Facter.add(:root_home) do + confine :kernel => :aix + root_home = nil + setcode do + str = Facter::Util::Resolution.exec("lsuser -c -a home root") + str && str.split("\n").each do |line| + next if line =~ /^#/ + root_home = line.split(/:/)[1] + end + root_home + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/service_provider.rb b/environments/prod/modules/stdlib/lib/facter/service_provider.rb new file mode 100644 index 0000000..a117921 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/service_provider.rb @@ -0,0 +1,17 @@ +# Fact: service_provider +# +# Purpose: Returns the default provider Puppet will choose to manage services +# on this system +# +# Resolution: Instantiates a dummy service resource and return the provider +# +# Caveats: +# +require 'puppet/type' +require 'puppet/type/service' + +Facter.add(:service_provider) do + setcode do + Puppet::Type.type(:service).newservice(:name => 'dummy')[:provider].to_s + end +end diff --git a/environments/prod/modules/stdlib/lib/facter/util/puppet_settings.rb b/environments/prod/modules/stdlib/lib/facter/util/puppet_settings.rb new file mode 100644 index 0000000..1ad9452 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/facter/util/puppet_settings.rb @@ -0,0 +1,21 @@ +module Facter + module Util + module PuppetSettings + # This method is intended to provide a convenient way to evaluate a + # Facter code block only if Puppet is loaded. This is to account for the + # situation where the fact happens to be in the load path, but Puppet is + # not loaded for whatever reason. Perhaps the user is simply running + # facter without the --puppet flag and they happen to be working in a lib + # directory of a module. + def self.with_puppet + begin + Module.const_get("Puppet") + rescue NameError + nil + else + yield + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/deprecation.rb b/environments/prod/modules/stdlib/lib/puppet/functions/deprecation.rb new file mode 100644 index 0000000..39d9bc7 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/deprecation.rb @@ -0,0 +1,29 @@ +# Function to print deprecation warnings, Logs a warning once for a given key. The uniqueness key - can appear once. The msg is the message text including any positional information that is formatted by the user/caller of the method It is affected by the puppet setting 'strict', which can be set to :error (outputs as an error message), :off (no message / error is displayed) and :warning (default, outputs a warning) *Type*: String, String. +# + +Puppet::Functions.create_function(:deprecation) do + dispatch :deprecation do + param 'String', :key + param 'String', :message + end + + def deprecation(key, message) + if defined? Puppet::Pops::PuppetStack.stacktrace() + stacktrace = Puppet::Pops::PuppetStack.stacktrace() + file = stacktrace[0] + line = stacktrace[1] + message = "#{message} at #{file}:#{line}" + end + # depending on configuration setting of strict + case Puppet.settings[:strict] + when :off + # do nothing + when :error + fail("deprecation. #{key}. #{message}") + else + unless ENV['STDLIB_LOG_DEPRECATIONS'] == 'false' + Puppet.deprecation_warning(message, key) + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_a.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_a.rb new file mode 100644 index 0000000..da98b03 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_a.rb @@ -0,0 +1,32 @@ +# Boolean check to determine whether a variable is of a given data type. This is equivalent to the `=~` type checks. +# +# @example how to check a data type +# # check a data type +# foo = 3 +# $bar = [1,2,3] +# $baz = 'A string!' +# +# if $foo.is_a(Integer) { +# notify { 'foo!': } +# } +# if $bar.is_a(Array) { +# notify { 'bar!': } +# } +# if $baz.is_a(String) { +# notify { 'baz!': } +# } +# +# See the documentation for "The Puppet Type System" for more information about types. +# See the `assert_type()` function for flexible ways to assert the type of a value. +# +Puppet::Functions.create_function(:is_a) do + dispatch :is_a do + param 'Any', :value + param 'Type', :type + end + + def is_a(value, type) + # See puppet's lib/puppet/pops/evaluator/evaluator_impl.rb eval_MatchExpression + Puppet::Pops::Types::TypeCalculator.instance?(type, value) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_absolute_path.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_absolute_path.rb new file mode 100644 index 0000000..b61064a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_absolute_path.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_absolute_path) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_absolute_path', "This method is deprecated, please use match expressions with Stdlib::Compat::Absolute_Path instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_absolute_path", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_array.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_array.rb new file mode 100644 index 0000000..a29fe8a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_array.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_array) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_array', "This method is deprecated, please use match expressions with Stdlib::Compat::Array instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_array", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_bool.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_bool.rb new file mode 100644 index 0000000..6e2c22b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_bool.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_bool) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_bool', "This method is deprecated, please use match expressions with Stdlib::Compat::Bool instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_bool", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_float.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_float.rb new file mode 100644 index 0000000..c91aa5d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_float.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_float) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_float', "This method is deprecated, please use match expressions with Stdlib::Compat::Float instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_float", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_ip_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_ip_address.rb new file mode 100644 index 0000000..4c72037 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_ip_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ip_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ip_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ip_address instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ip_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb new file mode 100644 index 0000000..97b01ae --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ipv4_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ipv4_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ipv4 instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ipv4_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb new file mode 100644 index 0000000..be0c98a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ipv6_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ipv4_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ipv6 instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ipv6_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_numeric.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_numeric.rb new file mode 100644 index 0000000..f5e9d41 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_numeric.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_numeric) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_numeric', "This method is deprecated, please use match expressions with Stdlib::Compat::Numeric instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_numeric", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/is_string.rb b/environments/prod/modules/stdlib/lib/puppet/functions/is_string.rb new file mode 100644 index 0000000..a05a796 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/is_string.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_string) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_string', "This method is deprecated, please use match expressions with Stdlib::Compat::String instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_string", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/length.rb b/environments/prod/modules/stdlib/lib/puppet/functions/length.rb new file mode 100644 index 0000000..86e735c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/length.rb @@ -0,0 +1,14 @@ +#A function to eventually replace the old size() function for stdlib - The original size function did not handle Puppets new type capabilities, so this function is a Puppet 4 compatible solution. +Puppet::Functions.create_function(:length) do + dispatch :length do + param 'Variant[String,Array,Hash]', :value + end + def length(value) + if value.is_a?(String) + result = value.length + elsif value.is_a?(Array) || value.is_a?(Hash) + result = value.size + end + return result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/type_of.rb b/environments/prod/modules/stdlib/lib/puppet/functions/type_of.rb new file mode 100644 index 0000000..01f1f49 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/type_of.rb @@ -0,0 +1,19 @@ +# Returns the type when passed a value. +# +# @example how to compare values' types +# # compare the types of two values +# if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") } +# @example how to compare against an abstract type +# unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") } +# unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") } +# +# See the documentation for "The Puppet Type System" for more information about types. +# See the `assert_type()` function for flexible ways to assert the type of a value. +# +# The built-in type() function in puppet is generally preferred over this function +# this function is provided for backwards compatibility. +Puppet::Functions.create_function(:type_of) do + def type_of(value) + Puppet::Pops::Types::TypeCalculator.infer_set(value) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb new file mode 100644 index 0000000..a3c696d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_absolute_path) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_absolute_path', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_Path. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_absolute_path", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_array.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_array.rb new file mode 100644 index 0000000..f59c6b4 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_array.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_array) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_array', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_array", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_bool.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_bool.rb new file mode 100644 index 0000000..5cfb2ac --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_bool.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_bool) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_bool', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_bool", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_hash.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_hash.rb new file mode 100644 index 0000000..89ad9ab --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_hash.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_hash) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_hash', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Hash. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_hash", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_integer.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_integer.rb new file mode 100644 index 0000000..475ea0f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_integer.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_integer) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_integer', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_integer", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_ip_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ip_address.rb new file mode 100644 index 0000000..1521c08 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ip_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ip_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ip_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_Address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ip_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb new file mode 100644 index 0000000..fe66ab3 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ipv4_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ipv4_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4_Address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ipv4_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb new file mode 100644 index 0000000..7cc3cbd --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ipv6_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ipv6_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6_address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ipv6_address", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_legacy.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_legacy.rb new file mode 100644 index 0000000..c9d1f56 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_legacy.rb @@ -0,0 +1,62 @@ +Puppet::Functions.create_function(:validate_legacy) do + # The function checks a value against both the target_type (new) and the previous_validation function (old). + + dispatch :validate_legacy do + param 'Any', :scope + param 'Type', :target_type + param 'String', :function_name + param 'Any', :value + repeated_param 'Any', :args + end + + dispatch :validate_legacy_s do + param 'Any', :scope + param 'String', :type_string + param 'String', :function_name + param 'Any', :value + repeated_param 'Any', :args + end + + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + + def validate_legacy_s(scope, type_string, *args) + t = Puppet::Pops::Types::TypeParser.new.parse(type_string, scope) + validate_legacy(scope, t, *args) + end + + def validate_legacy(scope, target_type, function_name, value, *prev_args) + if assert_type(target_type, value) + if previous_validation(scope, function_name, value, *prev_args) + # Silently passes + else + Puppet.notice("Accepting previously invalid value for target type '#{target_type}'") + end + else + inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value) + error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch("validate_legacy(#{function_name})", target_type, inferred_type) + if previous_validation(scope, function_name, value, *prev_args) + call_function('deprecation', 'validate_legacy', error_msg) + else + call_function('fail', error_msg) + end + end + end + + def previous_validation(scope, function_name, value, *prev_args) + # Call the previous validation function and catch any errors. Return true if no errors are thrown. + begin + scope.send("function_#{function_name}".to_s, [value, *prev_args]) + true + rescue Puppet::ParseError + false + end + end + + def assert_type(type, value) + Puppet::Pops::Types::TypeCalculator.instance?(type, value) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_numeric.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_numeric.rb new file mode 100644 index 0000000..3052d35 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_numeric.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_numeric) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_numeric', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_numeric", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_re.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_re.rb new file mode 100644 index 0000000..19443a8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_re.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_re) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_re', "This method is deprecated, please use the stdlib validate_legacy function, with Pattern[]. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_re", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_slength.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_slength.rb new file mode 100644 index 0000000..584232a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_slength.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_slength) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_slength', "This method is deprecated, please use the stdlib validate_legacy function, with String[]. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_slength", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/functions/validate_string.rb b/environments/prod/modules/stdlib/lib/puppet/functions/validate_string.rb new file mode 100644 index 0000000..91ff004 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/functions/validate_string.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_string) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_string', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_string", args) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/abs.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/abs.rb new file mode 100644 index 0000000..222a902 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/abs.rb @@ -0,0 +1,34 @@ +# +# abs.rb +# + +module Puppet::Parser::Functions + newfunction(:abs, :type => :rvalue, :doc => <<-EOS + Returns the absolute value of a number, for example -34.56 becomes + 34.56. Takes a single integer and float value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "abs(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + # Numbers in Puppet are often string-encoded which is troublesome ... + if value.is_a?(String) + if value.match(/^-?(?:\d+)(?:\.\d+){1}$/) + value = value.to_f + elsif value.match(/^-?\d+$/) + value = value.to_i + else + raise(Puppet::ParseError, 'abs(): Requires float or integer to work with') + end + end + + # We have numeric value to handle ... + result = value.abs + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2array.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2array.rb new file mode 100644 index 0000000..e71407e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2array.rb @@ -0,0 +1,33 @@ +# +# any2array.rb +# + +module Puppet::Parser::Functions + newfunction(:any2array, :type => :rvalue, :doc => <<-EOS +This converts any object to an array containing that object. Empty argument +lists are converted to an empty array. Arrays are left untouched. Hashes are +converted to arrays of alternating keys and values. + EOS + ) do |arguments| + + if arguments.empty? + return [] + end + + if arguments.length == 1 + if arguments[0].kind_of?(Array) + return arguments[0] + elsif arguments[0].kind_of?(Hash) + result = [] + arguments[0].each do |key, value| + result << key << value + end + return result + end + end + + return arguments + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2bool.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2bool.rb new file mode 100644 index 0000000..17612bf --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/any2bool.rb @@ -0,0 +1,54 @@ +# +# any2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:any2bool, :type => :rvalue, :doc => <<-EOS +This converts 'anything' to a boolean. In practise it does the following: + +* Strings such as Y,y,1,T,t,TRUE,yes,'true' will return true +* Strings such as 0,F,f,N,n,FALSE,no,'false' will return false +* Booleans will just return their original value +* Number (or a string representation of a number) > 0 will return true, otherwise false +* undef will return false +* Anything else will return true + EOS + ) do |arguments| + + raise(Puppet::ParseError, "any2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + # If argument is already Boolean, return it + if !!arguments[0] == arguments[0] + return arguments[0] + end + + arg = arguments[0] + + if arg == nil + return false + end + + if arg == :undef + return false + end + + valid_float = !!Float(arg) rescue false + + if arg.is_a?(Numeric) + return function_num2bool( [ arguments[0] ] ) + end + + if arg.is_a?(String) + if valid_float + return function_num2bool( [ arguments[0] ] ) + else + return function_str2bool( [ arguments[0] ] ) + end + end + + return true + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/assert_private.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/assert_private.rb new file mode 100644 index 0000000..62e2c6b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/assert_private.rb @@ -0,0 +1,28 @@ +# +# assert_private.rb +# + +module Puppet::Parser::Functions + newfunction(:assert_private, :doc => <<-'EOS' + Sets the current class or definition as private. + Calling the class or definition from outside the current module will fail. + EOS + ) do |args| + + raise(Puppet::ParseError, "assert_private(): Wrong number of arguments given (#{args.size}}) for 0 or 1)") if args.size > 1 + + scope = self + if scope.lookupvar('module_name') != scope.lookupvar('caller_module_name') + message = nil + if args[0] and args[0].is_a? String + message = args[0] + else + manifest_name = scope.source.name + manifest_type = scope.source.type + message = (manifest_type.to_s == 'hostclass') ? 'Class' : 'Definition' + message += " #{manifest_name} is private" + end + raise(Puppet::ParseError, message) + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/base64.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/base64.rb new file mode 100644 index 0000000..d610146 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/base64.rb @@ -0,0 +1,71 @@ + +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +module Puppet::Parser::Functions + + newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + + Base64 encode or decode a string based on the command and the string submitted + + Usage: + + $encodestring = base64('encode', 'thestring') + $decodestring = base64('decode', 'dGhlc3RyaW5n') + + # explicitly define encode/decode method: default, strict, urlsafe + $method = 'default' + $encodestring = base64('encode', 'thestring', $method) + $decodestring = base64('decode', 'dGhlc3RyaW5n', $method) + + ENDHEREDOC + + require 'base64' + + raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be >= 2)") unless args.length >= 2 + + actions = ['encode','decode'] + + unless actions.include?(args[0]) + raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'") + end + + unless args[1].is_a?(String) + raise Puppet::ParseError, ("base64(): the second argument must be a string to base64") + end + + method = ['default','strict','urlsafe'] + + if args.length <= 2 + chosenMethod = 'default' + else + chosenMethod = args[2] + end + + unless method.include?(chosenMethod) + raise Puppet::ParseError, ("base64(): the third argument must be one of 'default', 'strict', or 'urlsafe'") + end + + case args[0] + when 'encode' + case chosenMethod + when 'default' + result = Base64.encode64(args[1]) + when 'strict' + result = Base64.strict_encode64(args[1]) + when 'urlsafe' + result = Base64.urlsafe_encode64(args[1]) + end + when 'decode' + case chosenMethod + when 'default' + result = Base64.decode64(args[1]) + when 'strict' + result = Base64.strict_decode64(args[1]) + when 'urlsafe' + result = Base64.urlsafe_decode64(args[1]) + end + end + + return result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/basename.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/basename.rb new file mode 100644 index 0000000..f7e4438 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/basename.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:basename, :type => :rvalue, :doc => <<-EOS + Strips directory (and optional suffix) from a filename + EOS + ) do |arguments| + + if arguments.size < 1 then + raise(Puppet::ParseError, "basename(): No arguments given") + elsif arguments.size > 2 then + raise(Puppet::ParseError, "basename(): Too many arguments given (#{arguments.size})") + else + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'basename(): Requires string as first argument') + end + + if arguments.size == 1 then + rv = File.basename(arguments[0]) + elsif arguments.size == 2 then + + unless arguments[1].is_a?(String) + raise(Puppet::ParseError, 'basename(): Requires string as second argument') + end + + rv = File.basename(arguments[0], arguments[1]) + end + + end + + return rv + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2num.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2num.rb new file mode 100644 index 0000000..92e4dde --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2num.rb @@ -0,0 +1,25 @@ +# +# bool2num.rb +# + +module Puppet::Parser::Functions + newfunction(:bool2num, :type => :rvalue, :doc => <<-EOS + Converts a boolean to a number. Converts the values: + false, f, 0, n, and no to 0 + true, t, 1, y, and yes to 1 + Requires a single boolean or string as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "bool2num(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = function_str2bool([arguments[0]]) + + # We have real boolean values as well ... + result = value ? 1 : 0 + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2str.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2str.rb new file mode 100644 index 0000000..37d4a4e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/bool2str.rb @@ -0,0 +1,44 @@ +# +# bool2str.rb +# + +module Puppet::Parser::Functions + newfunction(:bool2str, :type => :rvalue, :doc => <<-EOS + Converts a boolean to a string using optionally supplied arguments. The + optional second and third arguments represent what true and false will be + converted to respectively. If only one argument is given, it will be + converted from a boolean to a string containing 'true' or 'false'. + + *Examples:* + + bool2str(true) => 'true' + bool2str(true, 'yes', 'no') => 'yes' + bool2str(false, 't', 'f') => 'f' + + Requires a single boolean as an input. + EOS + ) do |arguments| + + unless arguments.size == 1 or arguments.size == 3 + raise(Puppet::ParseError, "bool2str(): Wrong number of arguments given (#{arguments.size} for 3)") + end + + value = arguments[0] + true_string = arguments[1] || 'true' + false_string = arguments[2] || 'false' + klass = value.class + + # We can have either true or false, and nothing else + unless [FalseClass, TrueClass].include?(klass) + raise(Puppet::ParseError, 'bool2str(): Requires a boolean to work with') + end + + unless [true_string, false_string].all?{|x| x.kind_of?(String)} + raise(Puppet::ParseError, "bool2str(): Requires strings to convert to" ) + end + + return value ? true_string : false_string + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/camelcase.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/camelcase.rb new file mode 100644 index 0000000..085b3c6 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/camelcase.rb @@ -0,0 +1,32 @@ +# +# camelcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:camelcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to camel case. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "camelcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + klass = value.class + + unless [Array, String].include?(klass) + raise(Puppet::ParseError, 'camelcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.split('_').map{|e| e.capitalize}.join : i } + else + result = value.split('_').map{|e| e.capitalize}.join + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/capitalize.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/capitalize.rb new file mode 100644 index 0000000..096bc5d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/capitalize.rb @@ -0,0 +1,30 @@ +# +# capitalize.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:capitalize, :type => :rvalue, :doc => <<-EOS + Capitalizes the first letter of a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "capitalize(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'capitalize(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.capitalize : i } + else + result = value.capitalize + end + + return result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/ceiling.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ceiling.rb new file mode 100644 index 0000000..bec4266 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ceiling.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions + newfunction(:ceiling, :type => :rvalue, :doc => <<-EOS + Returns the smallest integer greater or equal to the argument. + Takes a single numeric value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "ceiling(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + begin + arg = Float(arguments[0]) + rescue TypeError, ArgumentError => e + raise(Puppet::ParseError, "ceiling(): Wrong argument type given (#{arguments[0]} for Numeric)") + end + + raise(Puppet::ParseError, "ceiling(): Wrong argument type given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false + + arg.ceil + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/chomp.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/chomp.rb new file mode 100644 index 0000000..f9da50f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/chomp.rb @@ -0,0 +1,32 @@ +# +# chomp.rb +# + +module Puppet::Parser::Functions + newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS' + Removes the record separator from the end of a string or an array of + strings, for example `hello\n` becomes `hello`. + Requires a single string or array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "chomp(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'chomp(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.chomp : i } + else + result = value.chomp + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/chop.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/chop.rb new file mode 100644 index 0000000..809349d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/chop.rb @@ -0,0 +1,34 @@ +# +# chop.rb +# + +module Puppet::Parser::Functions + newfunction(:chop, :type => :rvalue, :doc => <<-'EOS' + Returns a new string with the last character removed. If the string ends + with `\r\n`, both characters are removed. Applying chop to an empty + string returns an empty string. If you wish to merely remove record + separators then you should use the `chomp` function. + Requires a string or array of strings as input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "chop(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'chop(): Requires either an array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.chop : i } + else + result = value.chop + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/clamp.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/clamp.rb new file mode 100644 index 0000000..c4503fe --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/clamp.rb @@ -0,0 +1,29 @@ +# +# clamp.rb +# + +module Puppet::Parser::Functions + newfunction(:clamp, :type => :rvalue, :arity => -2, :doc => <<-EOS + Clamps value to a range. + EOS + ) do |args| + + args.flatten! + + raise(Puppet::ParseError, 'clamp(): Wrong number of arguments, need three to clamp') if args.size != 3 + + # check values out + args.each do |value| + case [value.class] + when [String] + raise(Puppet::ParseError, "clamp(): Required explicit numeric (#{value}:String)") unless value =~ /^\d+$/ + when [Hash] + raise(Puppet::ParseError, "clamp(): The Hash type is not allowed (#{value})") + end + end + + # convert to numeric each element + # then sort them and get a middle value + args.map{ |n| n.to_i }.sort[1] + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/concat.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/concat.rb new file mode 100644 index 0000000..0a49cfe --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/concat.rb @@ -0,0 +1,40 @@ +# +# concat.rb +# + +module Puppet::Parser::Functions + newfunction(:concat, :type => :rvalue, :doc => <<-EOS +Appends the contents of multiple arrays into array 1. + +*Example:* + + concat(['1','2','3'],['4','5','6'],['7','8','9']) + +Would result in: + + ['1','2','3','4','5','6','7','8','9'] + EOS + ) do |arguments| + + # Check that more than 2 arguments have been given ... + raise(Puppet::ParseError, "concat(): Wrong number of arguments given (#{arguments.size} for < 2)") if arguments.size < 2 + + a = arguments[0] + + # Check that the first parameter is an array + unless a.is_a?(Array) + raise(Puppet::ParseError, 'concat(): Requires array to work with') + end + + result = a + arguments.shift + + arguments.each do |x| + result = result + (x.is_a?(Array) ? x : [x]) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/convert_base.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/convert_base.rb new file mode 100644 index 0000000..0fcbafe --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/convert_base.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:convert_base, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| + + Converts a given integer or base 10 string representing an integer to a specified base, as a string. + + Usage: + + $binary_repr = convert_base(5, 2) # $binary_repr is now set to "101" + $hex_repr = convert_base("254", "16") # $hex_repr is now set to "fe" + + ENDHEREDOC + + raise Puppet::ParseError, ("convert_base(): First argument must be either a string or an integer") unless (args[0].is_a?(Integer) or args[0].is_a?(String)) + raise Puppet::ParseError, ("convert_base(): Second argument must be either a string or an integer") unless (args[1].is_a?(Integer) or args[1].is_a?(String)) + + if args[0].is_a?(String) + raise Puppet::ParseError, ("convert_base(): First argument must be an integer or a string corresponding to an integer in base 10") unless args[0] =~ /^[0-9]+$/ + end + + if args[1].is_a?(String) + raise Puppet::ParseError, ("convert_base(): First argument must be an integer or a string corresponding to an integer in base 10") unless args[1] =~ /^[0-9]+$/ + end + + number_to_convert = args[0] + new_base = args[1] + + number_to_convert = number_to_convert.to_i() + new_base = new_base.to_i() + + raise Puppet::ParseError, ("convert_base(): base must be at least 2 and must not be greater than 36") unless new_base >= 2 and new_base <= 36 + + return number_to_convert.to_s(new_base) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/count.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/count.rb new file mode 100644 index 0000000..cef2637 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/count.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS +Takes an array as first argument and an optional second argument. +Count the number of elements in array that matches second argument. +If called with only an array it counts the number of elements that are not nil/undef. + EOS + ) do |args| + + if (args.size > 2) then + raise(ArgumentError, "count(): Wrong number of arguments given #{args.size} for 1 or 2.") + end + + collection, item = args + + if item then + collection.count item + else + collection.count { |obj| obj != nil && obj != :undef && obj != '' } + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb new file mode 100644 index 0000000..6df32e9 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb @@ -0,0 +1,44 @@ +module Puppet::Parser::Functions + newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Recursively merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + + When there is a duplicate key that is a hash, they are recursively merged. + When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + deep_merge = Proc.new do |hash1,hash2| + hash1.merge(hash2) do |key,old_value,new_value| + if old_value.is_a?(Hash) && new_value.is_a?(Hash) + deep_merge.call(old_value, new_value) + else + new_value + end + end + end + + result = Hash.new + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + # If the argument was not a hash, skip it. + unless arg.is_a?(Hash) + raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments" + end + + result = deep_merge.call(result, arg) + end + return( result ) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb new file mode 100644 index 0000000..99687ae --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb @@ -0,0 +1,38 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:defined_with_params, + :type => :rvalue, + :doc => <<-'ENDOFDOC' +Takes a resource reference and an optional hash of attributes. + +Returns true if a resource with the specified attributes has already been added +to the catalog, and false otherwise. + + user { 'dan': + ensure => present, + } + + if ! defined_with_params(User[dan], {'ensure' => 'present' }) { + user { 'dan': ensure => present, } + } +ENDOFDOC +) do |vals| + reference, params = vals + raise(ArgumentError, 'Must specify a reference') unless reference + if (! params) || params == '' + params = {} + end + ret = false + if resource = findresource(reference.to_s) + matches = params.collect do |key, value| + # eql? avoids bugs caused by monkeypatching in puppet + resource_is_undef = resource[key].eql?(:undef) || resource[key].nil? + value_is_undef = value.eql?(:undef) || value.nil? + (resource_is_undef && value_is_undef) || (resource[key] == value) + end + ret = params.empty? || !matches.include?(false) + end + Puppet.debug("Resource #{reference} was not determined to be defined") + ret +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete.rb new file mode 100644 index 0000000..9dd5164 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete.rb @@ -0,0 +1,43 @@ +# +# delete.rb +# + +module Puppet::Parser::Functions + newfunction(:delete, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given element from an array, substring from a +string, or key from a hash. + +*Examples:* + + delete(['a','b','c','b'], 'b') + Would return: ['a','c'] + + delete({'a'=>1,'b'=>2,'c'=>3}, 'b') + Would return: {'a'=>1,'c'=>3} + + delete({'a'=>1,'b'=>2,'c'=>3}, ['b','c']) + Would return: {'a'=>1} + + delete('abracadabra', 'bra') + Would return: 'acada' + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete(): Wrong number of arguments given #{arguments.size} for 2") unless arguments.size == 2 + + collection = arguments[0].dup + Array(arguments[1]).each do |item| + case collection + when Array, Hash + collection.delete item + when String + collection.gsub! item, '' + else + raise(TypeError, "delete(): First argument must be an Array, String, or Hash. Given an argument of class #{collection.class}.") + end + end + collection + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_at.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_at.rb new file mode 100644 index 0000000..daf3721 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_at.rb @@ -0,0 +1,46 @@ +# +# delete_at.rb +# + +module Puppet::Parser::Functions + newfunction(:delete_at, :type => :rvalue, :doc => <<-EOS +Deletes a determined indexed value from an array. + +*Examples:* + + delete_at(['a','b','c'], 1) + +Would return: ['a','c'] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_at(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'delete_at(): Requires array to work with') + end + + index = arguments[1] + + if index.is_a?(String) and not index.match(/^\d+$/) + raise(Puppet::ParseError, 'delete_at(): You must provide non-negative numeric index') + end + + result = array.clone + + # Numbers in Puppet are often string-encoded which is troublesome ... + index = index.to_i + + if index > result.size - 1 # First element is at index 0 is it not? + raise(Puppet::ParseError, 'delete_at(): Given index exceeds size of array given') + end + + result.delete_at(index) # We ignore the element that got deleted ... + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb new file mode 100644 index 0000000..e5dc1fd --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb @@ -0,0 +1,44 @@ +# +# delete_regex.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:delete_regex, :type => :rvalue, :doc => <<-EOS +deletes all instances of a given element that match a regular expression +from an array or key from a hash. Multiple regular expressions are assumed +to be matched as an OR. + +*Examples:* + + delete_regex(['a','b','c','b'], 'b') + Would return: ['a','c'] + + delete_regex(['a','b','c','b'], ['b', 'c']) + Would return: ['a'] + + delete_regex({'a'=>1,'b'=>2,'c'=>3}, 'b') + Would return: {'a'=>1,'c'=>3} + + delete_regex({'a'=>1,'b'=>2,'c'=>3}, '^a$') + Would return: {'b'=>2,'c'=>3} + + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_regex(): Wrong number of arguments given #{arguments.size} for 2") unless arguments.size == 2 + + collection = arguments[0].dup + Array(arguments[1]).each do |item| + case collection + when Array, Hash, String + collection.reject! { |coll_item| (coll_item =~ %r{\b#{item}\b}) } + else + raise(TypeError, "delete_regex(): First argument must be an Array, Hash, or String. Given an argument of class #{collection.class}.") + end + end + collection + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb new file mode 100644 index 0000000..00bd252 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb @@ -0,0 +1,31 @@ +module Puppet::Parser::Functions + newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS +Returns a copy of input hash or array with all undefs deleted. + +*Examples:* + + $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + +Would return: {a => 'A', b => '', d => false} + + $array = delete_undef_values(['A','',undef,false]) + +Would return: ['A','',false] + + EOS + ) do |args| + + raise(Puppet::ParseError, "delete_undef_values(): Wrong number of arguments given (#{args.size})") if args.size < 1 + + unless args[0].is_a? Array or args[0].is_a? Hash + raise(Puppet::ParseError, "delete_undef_values(): expected an array or hash, got #{args[0]} type #{args[0].class} ") + end + result = args[0].dup + if result.is_a?(Hash) + result.delete_if {|key, val| val.equal? :undef} + elsif result.is_a?(Array) + result.delete :undef + end + result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_values.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_values.rb new file mode 100644 index 0000000..e799aef --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/delete_values.rb @@ -0,0 +1,23 @@ +module Puppet::Parser::Functions + newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given value from a hash. + +*Examples:* + + delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B') + +Would return: {'a'=>'A','c'=>'C','B'=>'D'} + + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_values(): Wrong number of arguments given (#{arguments.size} of 2)") if arguments.size != 2 + + hash, item = arguments + + if not hash.is_a?(Hash) + raise(TypeError, "delete_values(): First argument must be a Hash. Given an argument of class #{hash.class}.") + end + hash.dup.delete_if { |key, val| item == val } + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/deprecation.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/deprecation.rb new file mode 100644 index 0000000..39d306a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/deprecation.rb @@ -0,0 +1,16 @@ +module Puppet::Parser::Functions + newfunction(:deprecation, :doc => <<-EOS + Function to print deprecation warnings (this is the 3.X version of it), The uniqueness key - can appear once. The msg is the message text including any positional information that is formatted by the user/caller of the method.). +EOS + ) do |arguments| + + raise(Puppet::ParseError, "deprecation: Wrong number of arguments given (#{arguments.size} for 2)") unless arguments.size == 2 + + key = arguments[0] + message = arguments[1] + + if ENV['STDLIB_LOG_DEPRECATIONS'] == "true" + warning("deprecation. #{key}. #{message}") + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/difference.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/difference.rb new file mode 100644 index 0000000..c9ac478 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/difference.rb @@ -0,0 +1,35 @@ +# +# difference.rb +# + +module Puppet::Parser::Functions + newfunction(:difference, :type => :rvalue, :doc => <<-EOS +This function returns the difference between two arrays. +The returned array is a copy of the original array, removing any items that +also appear in the second array. + +*Examples:* + + difference(["a","b","c"],["b","c","d"]) + +Would return: ["a"] + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "difference(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'difference(): Requires 2 arrays') + end + + result = first - second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig.rb new file mode 100644 index 0000000..34fa701 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig.rb @@ -0,0 +1,16 @@ +# +# dig.rb +# + +module Puppet::Parser::Functions + newfunction(:dig, :type => :rvalue, :doc => <<-EOS + DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version. + EOS + ) do |arguments| + warning("dig() DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version.") + if ! Puppet::Parser::Functions.autoloader.loaded?(:dig44) + Puppet::Parser::Functions.autoloader.load(:dig44) + end + function_dig44(arguments) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig44.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig44.rb new file mode 100644 index 0000000..21c0a8c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dig44.rb @@ -0,0 +1,70 @@ +# +# dig44.rb +# + +module Puppet::Parser::Functions + newfunction( + :dig44, + :type => :rvalue, + :arity => -2, + :doc => <<-eos +DEPRECATED: This function has been replaced in puppet 4.5.0. + +Looks up into a complex structure of arrays and hashes and returns a value +or the default value if nothing was found. + +Key can contain slashes to describe path components. The function will go down +the structure and try to extract the required value. + +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig44($data, ['a', 'b', '2'], 'not_found') +=> $value = 'b3' + +a -> first hash key +b -> second hash key +2 -> array index starting with 0 + +not_found -> (optional) will be returned if there is no value or the path +did not match. Defaults to nil. + +In addition to the required "key" argument, the function accepts a default +argument. It will be returned if no value was found or a path component is +missing. And the fourth argument can set a variable path separator. + eos + ) do |arguments| + # Two arguments are required + raise(Puppet::ParseError, "dig44(): Wrong number of arguments given (#{arguments.size} for at least 2)") if arguments.size < 2 + + data, path, default = *arguments + + unless data.is_a?(Hash) or data.is_a?(Array) + raise(Puppet::ParseError, "dig44(): first argument must be a hash or an array, given #{data.class.name}") + end + + unless path.is_a? Array + raise(Puppet::ParseError, "dig44(): second argument must be an array, given #{path.class.name}") + end + + value = path.reduce(data) do |structure, key| + if structure.is_a? Hash or structure.is_a? Array + if structure.is_a? Array + key = Integer key rescue break + end + break if structure[key].nil? or structure[key] == :undef + structure[key] + else + break + end + end + value.nil? ? default : value + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/dirname.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dirname.rb new file mode 100644 index 0000000..40b300d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dirname.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:dirname, :type => :rvalue, :doc => <<-EOS + Returns the dirname of a path. + EOS + ) do |arguments| + + if arguments.size < 1 then + raise(Puppet::ParseError, "dirname(): No arguments given") + end + if arguments.size > 1 then + raise(Puppet::ParseError, "dirname(): Too many arguments given (#{arguments.size})") + end + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'dirname(): Requires string as argument') + end + + return File.dirname(arguments[0]) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb new file mode 100644 index 0000000..ccac899 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb @@ -0,0 +1,15 @@ +# Custom Puppet function to convert dos to unix format +module Puppet::Parser::Functions + newfunction(:dos2unix, :type => :rvalue, :arity => 1, :doc => <<-EOS + Returns the Unix version of the given string. + Takes a single string argument. + EOS + ) do |arguments| + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'dos2unix(): Requires string as argument') + end + + arguments[0].gsub(/\r\n/, "\n") + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/downcase.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/downcase.rb new file mode 100644 index 0000000..ab04f8a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/downcase.rb @@ -0,0 +1,31 @@ +# +# downcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:downcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to lower case. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "downcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'downcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.downcase : i } + else + result = value.downcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/empty.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/empty.rb new file mode 100644 index 0000000..4f77ad3 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/empty.rb @@ -0,0 +1,29 @@ +# +# empty.rb +# + +module Puppet::Parser::Functions + newfunction(:empty, :type => :rvalue, :doc => <<-EOS +Returns true if the variable is empty. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "empty(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(String) || value.is_a?(Numeric) + raise(Puppet::ParseError, 'empty(): Requires either array, hash, string or integer to work with') + end + + if value.is_a?(Numeric) + return false + else + result = value.empty? + + return result + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb new file mode 100644 index 0000000..1f8a454 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb @@ -0,0 +1,42 @@ +# +# enclose_ipv6.rb +# + +module Puppet::Parser::Functions + newfunction(:enclose_ipv6, :type => :rvalue, :doc => <<-EOS +Takes an array of ip addresses and encloses the ipv6 addresses with square brackets. + EOS + ) do |arguments| + + require 'ipaddr' + + rescuable_exceptions = [ ArgumentError ] + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + if (arguments.size != 1) then + raise(Puppet::ParseError, "enclose_ipv6(): Wrong number of arguments given #{arguments.size} for 1") + end + unless arguments[0].is_a?(String) or arguments[0].is_a?(Array) then + raise(Puppet::ParseError, "enclose_ipv6(): Wrong argument type given #{arguments[0].class} expected String or Array") + end + + input = [arguments[0]].flatten.compact + result = [] + + input.each do |val| + unless val == '*' + begin + ip = IPAddr.new(val) + rescue *rescuable_exceptions + raise(Puppet::ParseError, "enclose_ipv6(): Wrong argument given #{val} is not an ip address.") + end + val = "[#{ip.to_s}]" if ip.ipv6? + end + result << val + end + + return result.uniq + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb new file mode 100644 index 0000000..17942b8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb @@ -0,0 +1,47 @@ +# +# ensure_packages.rb +# + +module Puppet::Parser::Functions + newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS +Takes a list of packages and only installs them if they don't already exist. +It optionally takes a hash as a second parameter that will be passed as the +third argument to the ensure_resource() function. + EOS + ) do |arguments| + + if arguments.size > 2 or arguments.size == 0 + raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments given (#{arguments.size} for 1 or 2)") + elsif arguments.size == 2 and !arguments[1].is_a?(Hash) + raise(Puppet::ParseError, 'ensure_packages(): Requires second argument to be a Hash') + end + + if arguments[0].is_a?(Hash) + if arguments[1] + defaults = { 'ensure' => 'present' }.merge(arguments[1]) + else + defaults = { 'ensure' => 'present' } + end + + Puppet::Parser::Functions.function(:ensure_resources) + function_ensure_resources(['package', arguments[0].dup, defaults ]) + else + packages = Array(arguments[0]) + + if arguments[1] + defaults = { 'ensure' => 'present' }.merge(arguments[1]) + else + defaults = { 'ensure' => 'present' } + end + + Puppet::Parser::Functions.function(:ensure_resource) + packages.each { |package_name| + if !findresource("Package[#{package_name}]") + function_ensure_resource(['package', package_name, defaults ]) + end + } + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb new file mode 100644 index 0000000..1ba6a44 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb @@ -0,0 +1,46 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:ensure_resource, + :type => :statement, + :doc => <<-'ENDOFDOC' +Takes a resource type, title, and a list of attributes that describe a +resource. + + user { 'dan': + ensure => present, + } + +This example only creates the resource if it does not already exist: + + ensure_resource('user', 'dan', {'ensure' => 'present' }) + +If the resource already exists but does not match the specified parameters, +this function will attempt to recreate the resource leading to a duplicate +resource definition error. + +An array of resources can also be passed in and each will be created with +the type and parameters specified if it doesn't already exist. + + ensure_resource('user', ['dan','alex'], {'ensure' => 'present'}) + +ENDOFDOC +) do |vals| + type, title, params = vals + raise(ArgumentError, 'Must specify a type') unless type + raise(ArgumentError, 'Must specify a title') unless title + params ||= {} + + items = [title].flatten + + items.each do |item| + Puppet::Parser::Functions.function(:defined_with_params) + if function_defined_with_params(["#{type}[#{item}]", params]) + Puppet.debug("Resource #{type}[#{item}] with params #{params} not created because it already exists") + else + Puppet.debug("Create new resource #{type}[#{item}] with params #{params}") + Puppet::Parser::Functions.function(:create_resources) + function_create_resources([type.capitalize, { item => params }]) + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb new file mode 100644 index 0000000..b3c51e6 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb @@ -0,0 +1,54 @@ +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:ensure_resources, + :type => :statement, + :doc => <<-'ENDOFDOC' +Takes a resource type, title (only hash), and a list of attributes that describe a +resource. + + user { 'dan': + gid => 'mygroup', + ensure => present, + } + +An hash of resources should be passed in and each will be created with +the type and parameters specified if it doesn't already exist. + + ensure_resources('user', {'dan' => { gid => 'mygroup', uid => '600' } , 'alex' => { gid => 'mygroup' }}, {'ensure' => 'present'}) + +From Hiera Backend: + +userlist: + dan: + gid: 'mygroup' + uid: '600' + alex: + gid: 'mygroup' + +Call: +ensure_resources('user', hiera_hash('userlist'), {'ensure' => 'present'}) + +ENDOFDOC +) do |vals| + type, title, params = vals + raise(ArgumentError, 'Must specify a type') unless type + raise(ArgumentError, 'Must specify a title') unless title + params ||= {} + + if title.is_a?(Hash) + resource_hash = title.dup + resources = resource_hash.keys + + Puppet::Parser::Functions.function(:ensure_resource) + resources.each { |resource_name| + if resource_hash[resource_name] + params_merged = params.merge(resource_hash[resource_name]) + else + params_merged = params + end + function_ensure_resource([ type, resource_name, params_merged ]) + } + else + raise(Puppet::ParseError, 'ensure_resources(): Requires second argument to be a Hash') + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/flatten.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/flatten.rb new file mode 100644 index 0000000..4401bdb --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/flatten.rb @@ -0,0 +1,32 @@ +# +# flatten.rb +# + +module Puppet::Parser::Functions + newfunction(:flatten, :type => :rvalue, :doc => <<-EOS +This function flattens any deeply nested arrays and returns a single flat array +as a result. + +*Examples:* + + flatten(['a', ['b', ['c']]]) + +Would return: ['a','b','c'] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "flatten(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'flatten(): Requires array to work with') + end + + result = array.flatten + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/floor.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/floor.rb new file mode 100644 index 0000000..9e4b504 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/floor.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions + newfunction(:floor, :type => :rvalue, :doc => <<-EOS + Returns the largest integer less or equal to the argument. + Takes a single numeric value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "floor(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + begin + arg = Float(arguments[0]) + rescue TypeError, ArgumentError => e + raise(Puppet::ParseError, "floor(): Wrong argument type given (#{arguments[0]} for Numeric)") + end + + raise(Puppet::ParseError, "floor(): Wrong argument type given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false + + arg.floor + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb new file mode 100644 index 0000000..2bb1287 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb @@ -0,0 +1,34 @@ +Puppet::Parser::Functions::newfunction( + :fqdn_rand_string, + :arity => -2, + :type => :rvalue, + :doc => "Usage: `fqdn_rand_string(LENGTH, [CHARSET], [SEED])`. LENGTH is + required and must be a positive integer. CHARSET is optional and may be + `undef` or a string. SEED is optional and may be any number or string. + + Generates a random string LENGTH characters long using the character set + provided by CHARSET, combining the `$fqdn` fact and the value of SEED for + repeatable randomness. (That is, each node will get a different random + string from this function, but a given node's result will be the same every + time unless its hostname changes.) Adding a SEED can be useful if you need + more than one unrelated string. CHARSET will default to alphanumeric if + `undef` or an empty string.") do |args| + raise(ArgumentError, "fqdn_rand_string(): wrong number of arguments (0 for 1)") if args.size == 0 + Puppet::Parser::Functions.function('is_integer') + raise(ArgumentError, "fqdn_rand_string(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0 + raise(ArgumentError, "fqdn_rand_string(): second argument must be undef or a string") unless args[1].nil? or args[1].is_a? String + + Puppet::Parser::Functions.function('fqdn_rand') + + length = args.shift.to_i + charset = args.shift.to_s.chars.to_a + + charset = (0..9).map { |i| i.to_s } + ('A'..'Z').to_a + ('a'..'z').to_a if charset.empty? + + rand_string = '' + for current in 1..length + rand_string << charset[function_fqdn_rand([charset.size, (args + [current.to_s]).join(':')]).to_i] + end + + rand_string +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb new file mode 100644 index 0000000..05bdcc7 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb @@ -0,0 +1,61 @@ +# +# fqdn_rotate.rb +# + +Puppet::Parser::Functions.newfunction( + :fqdn_rotate, + :type => :rvalue, + :doc => "Usage: `fqdn_rotate(VALUE, [SEED])`. VALUE is required and + must be an array or a string. SEED is optional and may be any number + or string. + + Rotates VALUE a random number of times, combining the `$fqdn` fact and + the value of SEED for repeatable randomness. (That is, each node will + get a different random rotation from this function, but a given node's + result will be the same every time unless its hostname changes.) Adding + a SEED can be useful if you need more than one unrelated rotation.") do |args| + + raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments given (#{args.size} for 1)") if args.size < 1 + + value = args.shift + require 'digest/md5' + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'fqdn_rotate(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # Check whether it makes sense to rotate ... + return result if result.size <= 1 + + # We turn any string value into an array to be able to rotate ... + result = string ? result.split('') : result + + elements = result.size + + seed = Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex + # deterministic_rand() was added in Puppet 3.2.0; reimplement if necessary + if Puppet::Util.respond_to?(:deterministic_rand) + offset = Puppet::Util.deterministic_rand(seed, elements).to_i + else + if defined?(Random) == 'constant' && Random.class == Class + offset = Random.new(seed).rand(elements) + else + old_seed = srand(seed) + offset = rand(elements) + srand(old_seed) + end + end + offset.times { + result.push result.shift + } + + result = string ? result.join : result + + return result +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb new file mode 100644 index 0000000..30205d0 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb @@ -0,0 +1,92 @@ +require 'digest/sha1' + +module Puppet::Parser::Functions + newfunction(:fqdn_uuid, :type => :rvalue, :doc => <<-END) do |args| + + Creates a UUID based on a given string, assumed to be the FQDN + + For example, to generate a UUID based on the FQDN of a system: + + Usage: + + $uuid = fqdn_uuid($::fqdn) + + The generated UUID will be the same for the given hostname + + The resulting UUID is returned on the form: + + 1d839dea-5e10-5243-88eb-e66815bd7d5c + + (u.e. without any curly braces.) + + The generated UUID is a version 5 UUID with the V5 DNS namespace: + + 6ba7b810-9dad-11d1-80b4-00c04fd430c8 + + This only supports a the V5 SHA-1 hash, using the DNS namespace. + + Please consult http://www.ietf.org/rfc/rfc4122.txt for the details on + UUID generation and example implementation. + + No verification is present at the moment as whether the domain name given + is in fact a correct fully-qualified domain name. Therefore any arbitrary + string and/or alpha-numeric value can subside for a domain name. + EOS + + END + + if args.length == 0 + raise(ArgumentError, "fqdn_uuid: No arguments given") + elsif args.length == 1 + fqdn = args[0] + else + raise(ArgumentError, "fqdn_uuid: Too many arguments given (#{args.length})") + end + + # Code lovingly taken from + # https://github.com/puppetlabs/marionette-collective/blob/master/lib/mcollective/ssl.rb + + # This is the UUID version 5 type DNS name space which is as follows: + # + # 6ba7b810-9dad-11d1-80b4-00c04fd430c8 + # + uuid_name_space_dns = [0x6b, + 0xa7, + 0xb8, + 0x10, + 0x9d, + 0xad, + 0x11, + 0xd1, + 0x80, + 0xb4, + 0x00, + 0xc0, + 0x4f, + 0xd4, + 0x30, + 0xc8 + ].map {|b| b.chr}.join + + sha1 = Digest::SHA1.new + sha1.update(uuid_name_space_dns) + sha1.update(fqdn) + + # first 16 bytes.. + bytes = sha1.digest[0, 16].bytes.to_a + + # version 5 adjustments + bytes[6] &= 0x0f + bytes[6] |= 0x50 + + # variant is DCE 1.1 + bytes[8] &= 0x3f + bytes[8] |= 0x80 + + bytes = [4, 2, 2, 2, 6].collect do |i| + bytes.slice!(0, i).pack('C*').unpack('H*') + end + + bytes.join('-') + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb new file mode 100644 index 0000000..1421b91 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb @@ -0,0 +1,17 @@ +module Puppet::Parser::Functions + newfunction(:get_module_path, :type =>:rvalue, :doc => <<-EOT + Returns the absolute path of the specified module for the current + environment. + + Example: + $module_path = get_module_path('stdlib') + EOT + ) do |args| + raise(Puppet::ParseError, "get_module_path(): Wrong number of arguments, expects one") unless args.size == 1 + if module_path = Puppet::Module.find(args[0], compiler.environment.to_s) + module_path.path + else + raise(Puppet::ParseError, "Could not find module #{args[0]} in environment #{compiler.environment}") + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/getparam.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/getparam.rb new file mode 100644 index 0000000..0a5cbe0 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/getparam.rb @@ -0,0 +1,35 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:getparam, + :type => :rvalue, + :doc => <<-'ENDOFDOC' +Takes a resource reference and name of the parameter and +returns value of resource's parameter. + +*Examples:* + + define example_resource($param) { + } + + example_resource { "example_resource_instance": + param => "param_value" + } + + getparam(Example_resource["example_resource_instance"], "param") + +Would return: param_value +ENDOFDOC +) do |vals| + reference, param = vals + raise(ArgumentError, 'Must specify a reference') unless reference + raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String + + return '' if param.empty? + + if resource = findresource(reference.to_s) + return resource[param] unless resource[param].nil? + end + + return '' +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/getvar.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/getvar.rb new file mode 100644 index 0000000..3af8d48 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/getvar.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:getvar, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Lookup a variable in a remote namespace. + + For example: + + $foo = getvar('site::data::foo') + # Equivalent to $foo = $site::data::foo + + This is useful if the namespace itself is stored in a string: + + $datalocation = 'site::data' + $bar = getvar("${datalocation}::bar") + # Equivalent to $bar = $site::data::bar + ENDHEREDOC + + unless args.length == 1 + raise Puppet::ParseError, ("getvar(): wrong number of arguments (#{args.length}; must be 1)") + end + + begin + result = nil + catch(:undefined_variable) do + result = self.lookupvar("#{args[0]}") + end + + # avoid relying on incosistent behaviour around ruby return values from catch + result + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/grep.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/grep.rb new file mode 100644 index 0000000..c611a7e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/grep.rb @@ -0,0 +1,32 @@ +# +# grep.rb +# + +module Puppet::Parser::Functions + newfunction(:grep, :type => :rvalue, :doc => <<-EOS +This function searches through an array and returns any elements that match +the provided regular expression. + +*Examples:* + + grep(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + + ['aaa','aaaddd'] + EOS + ) do |arguments| + + if (arguments.size != 2) then + raise(Puppet::ParseError, "grep(): Wrong number of arguments given #{arguments.size} for 2") + end + + a = arguments[0] + pattern = Regexp.new(arguments[1]) + + a.grep(pattern) + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb new file mode 100644 index 0000000..f6cb74b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb @@ -0,0 +1,70 @@ +# +# has_interface_with +# + +module Puppet::Parser::Functions + newfunction(:has_interface_with, :type => :rvalue, :doc => <<-EOS +Returns boolean based on kind and value: + * macaddress + * netmask + * ipaddress + * network + +has_interface_with("macaddress", "x:x:x:x:x:x") +has_interface_with("ipaddress", "127.0.0.1") => true +etc. + +If no "kind" is given, then the presence of the interface is checked: +has_interface_with("lo") => true + EOS + ) do |args| + + raise(Puppet::ParseError, "has_interface_with(): Wrong number of arguments given (#{args.size} for 1 or 2)") if args.size < 1 or args.size > 2 + + interfaces = lookupvar('interfaces') + + # If we do not have any interfaces, then there are no requested attributes + return false if (interfaces == :undefined || interfaces.nil?) + + interfaces = interfaces.split(',') + + if args.size == 1 + return interfaces.member?(args[0]) + end + + kind, value = args + + # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable + # https://tickets.puppetlabs.com/browse/PUP-3597 + factval = nil + begin + catch :undefined_variable do + factval = lookupvar(kind) + end + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + if factval == value + return true + end + + result = false + interfaces.each do |iface| + iface.downcase! + factval = nil + begin + # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable + # https://tickets.puppetlabs.com/browse/PUP-3597 + catch :undefined_variable do + factval = lookupvar("#{kind}_#{iface}") + end + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + if value == factval + result = true + break + end + end + + result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb new file mode 100644 index 0000000..a0071c8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb @@ -0,0 +1,24 @@ +# +# has_ip_address +# + +module Puppet::Parser::Functions + newfunction(:has_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the client has the requested IP address on some interface. + +This function iterates through the 'interfaces' fact and checks the +'ipaddress_IFACE' facts, performing a simple string comparison. + EOS + ) do |args| + + raise(Puppet::ParseError, "has_ip_address(): Wrong number of arguments given (#{args.size} for 1)") if args.size != 1 + + Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ + unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + + function_has_interface_with(['ipaddress', args[0]]) + + end +end + +# vim:sts=2 sw=2 diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb new file mode 100644 index 0000000..b02c0c0 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb @@ -0,0 +1,24 @@ +# +# has_ip_network +# + +module Puppet::Parser::Functions + newfunction(:has_ip_network, :type => :rvalue, :doc => <<-EOS +Returns true if the client has an IP address within the requested network. + +This function iterates through the 'interfaces' fact and checks the +'network_IFACE' facts, performing a simple string comparision. + EOS + ) do |args| + + raise(Puppet::ParseError, "has_ip_network(): Wrong number of arguments given (#{args.size} for 1)") if args.size != 1 + + Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ + unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + + function_has_interface_with(['network', args[0]]) + + end +end + +# vim:sts=2 sw=2 diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_key.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_key.rb new file mode 100644 index 0000000..4657cc2 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/has_key.rb @@ -0,0 +1,28 @@ +module Puppet::Parser::Functions + + newfunction(:has_key, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Determine if a hash has a certain key value. + + Example: + + $my_hash = {'key_one' => 'value_one'} + if has_key($my_hash, 'key_two') { + notice('we will not reach here') + } + if has_key($my_hash, 'key_one') { + notice('this will be printed') + } + + ENDHEREDOC + + unless args.length == 2 + raise Puppet::ParseError, ("has_key(): wrong number of arguments (#{args.length}; must be 2)") + end + unless args[0].is_a?(Hash) + raise Puppet::ParseError, "has_key(): expects the first argument to be a hash, got #{args[0].inspect} which is of type #{args[0].class}" + end + args[0].has_key?(args[1]) + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/hash.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/hash.rb new file mode 100644 index 0000000..22763f3 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/hash.rb @@ -0,0 +1,39 @@ +# +# hash.rb +# + +module Puppet::Parser::Functions + newfunction(:hash, :type => :rvalue, :doc => <<-EOS +This function converts an array into a hash. + +*Examples:* + + hash(['a',1,'b',2,'c',3]) + +Would return: {'a'=>1,'b'=>2,'c'=>3} + EOS + ) do |arguments| + + raise(Puppet::ParseError, "hash(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'hash(): Requires array to work with') + end + + result = {} + + begin + # This is to make it compatible with older version of Ruby ... + array = array.flatten + result = Hash[*array] + rescue StandardError + raise(Puppet::ParseError, 'hash(): Unable to compute hash from array given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/intersection.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/intersection.rb new file mode 100644 index 0000000..8a438f4 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/intersection.rb @@ -0,0 +1,33 @@ +# +# intersection.rb +# + +module Puppet::Parser::Functions + newfunction(:intersection, :type => :rvalue, :doc => <<-EOS +This function returns an array of the intersection of two. + +*Examples:* + + intersection(["a","b","c"],["b","c","d"]) # returns ["b","c"] + intersection(["a","b","c"],[1,2,3,4]) # returns [] (true, when evaluated as a Boolean) + + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "intersection(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'intersection(): Requires 2 arrays') + end + + result = first & second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb new file mode 100644 index 0000000..e64777f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + newfunction(:is_absolute_path, :type => :rvalue, :arity => 1, :doc => <<-'ENDHEREDOC') do |args| + Returns boolean true if the string represents an absolute path in the filesystem. This function works + for windows and unix style paths. + + The following values will return true: + + $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' + is_absolute_path($my_path) + $my_path2 = '/var/lib/puppet' + is_absolute_path($my_path2) + $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet'] + is_absolute_path($my_path3) + $my_path4 = ['/var/lib/puppet'] + is_absolute_path($my_path4) + + The following values will return false: + + is_absolute_path(true) + is_absolute_path('../var/lib/puppet') + is_absolute_path('var/lib/puppet') + $undefined = undef + is_absolute_path($undefined) + + ENDHEREDOC + function_deprecation([:is_absolute_path, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_path. There is further documentation for validate_legacy function in the README.']) + require 'puppet/util' + + path = args[0] + # This logic was borrowed from + # [lib/puppet/file_serving/base.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/file_serving/base.rb) + # Puppet 2.7 and beyond will have Puppet::Util.absolute_path? Fall back to a back-ported implementation otherwise. + if Puppet::Util.respond_to?(:absolute_path?) then + value = (Puppet::Util.absolute_path?(path, :posix) or Puppet::Util.absolute_path?(path, :windows)) + else + # This code back-ported from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path? + # Determine in a platform-specific way whether a path is absolute. This + # defaults to the local platform if none is specified. + # Escape once for the string literal, and once for the regex. + slash = '[\\\\/]' + name = '[^\\\\/]+' + regexes = { + :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, + :posix => %r!^/! + } + value = (!!(path =~ regexes[:posix])) || (!!(path =~ regexes[:windows])) + end + value + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_array.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_array.rb new file mode 100644 index 0000000..a33afe4 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_array.rb @@ -0,0 +1,21 @@ +# +# is_array.rb +# + +module Puppet::Parser::Functions + newfunction(:is_array, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an array. + EOS + ) do |arguments| + + function_deprecation([:is_array, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_array(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + type = arguments[0] + + result = type.is_a?(Array) + + return result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_bool.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_bool.rb new file mode 100644 index 0000000..2ebe430 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_bool.rb @@ -0,0 +1,21 @@ +# +# is_bool.rb +# + +module Puppet::Parser::Functions + newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a boolean. + EOS + ) do |arguments| + + function_deprecation([:is_bool, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + type = arguments[0] + + result = type.is_a?(TrueClass) || type.is_a?(FalseClass) + + return result + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb new file mode 100644 index 0000000..247db3b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb @@ -0,0 +1,53 @@ +# +# is_domain_name.rb +# + +module Puppet::Parser::Functions + newfunction(:is_domain_name, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a syntactically correct domain name. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_domain_name(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Only allow string types + return false unless arguments[0].is_a?(String) + + domain = arguments[0].dup + + # Limits (rfc1035, 3.1) + domain_max_length=255 + label_min_length=1 + label_max_length=63 + + # Allow ".", it is the top level domain + return true if domain == '.' + + # Remove the final dot, if present. + domain.chomp!('.') + + # Check the whole domain + return false if domain.empty? + return false if domain.length > domain_max_length + + # The top level domain must be alphabetic if there are multiple labels. + # See rfc1123, 2.1 + return false if domain.include? '.' and not /\.[A-Za-z]+$/.match(domain) + + # Check each label in the domain + labels = domain.split('.') + vlabels = labels.each do |label| + break if label.length < label_min_length + break if label.length > label_max_length + break if label[-1..-1] == '-' + break if label[0..0] == '-' + break unless /^[a-z\d-]+$/i.match(label) + end + return vlabels == labels + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb new file mode 100644 index 0000000..bcd7921 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb @@ -0,0 +1,20 @@ +# +# is_email_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_email_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid email address. + EOS + ) do |arguments| + if arguments.size != 1 + raise(Puppet::ParseError, "is_email_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Taken from http://emailregex.com/ (simpler regex) + valid_email_regex = %r{\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z} + return (arguments[0] =~ valid_email_regex) == 0 + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_float.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_float.rb new file mode 100644 index 0000000..aa84a2b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_float.rb @@ -0,0 +1,31 @@ +# +# is_float.rb +# + +module Puppet::Parser::Functions + newfunction(:is_float, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a float. + EOS + ) do |arguments| + + function_deprecation([:is_float, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Float. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_float(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Only allow Numeric or String types + return false unless value.is_a?(Numeric) or value.is_a?(String) + + if value != value.to_f.to_s and !value.is_a? Float then + return false + else + return true + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb new file mode 100644 index 0000000..50cd5e1 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb @@ -0,0 +1,25 @@ +# +# is_function_available.rb +# + +module Puppet::Parser::Functions + newfunction(:is_function_available, :type => :rvalue, :doc => <<-EOS +This function accepts a string as an argument, determines whether the +Puppet runtime has access to a function by that name. It returns a +true if the function exists, false if not. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_function_available?(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Only allow String types + return false unless arguments[0].is_a?(String) + + function = Puppet::Parser::Functions.function(arguments[0].to_sym) + function.is_a?(String) and not function.empty? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_hash.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_hash.rb new file mode 100644 index 0000000..3162f7d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_hash.rb @@ -0,0 +1,21 @@ +# +# is_hash.rb +# + +module Puppet::Parser::Functions + newfunction(:is_hash, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a hash. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "is_hash(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + type = arguments[0] + + result = type.is_a?(Hash) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_integer.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_integer.rb new file mode 100644 index 0000000..8965b15 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_integer.rb @@ -0,0 +1,46 @@ +# +# is_integer.rb +# + +module Puppet::Parser::Functions + newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an Integer or +a decimal (base 10) integer in String form. The string may +start with a '-' (minus). A value of '0' is allowed, but a leading '0' digit may not +be followed by other digits as this indicates that the value is octal (base 8). + +If given any other argument `false` is returned. + EOS + ) do |arguments| + + function_deprecation([:is_integer, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_integer(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Regex is taken from the lexer of puppet + # puppet/pops/parser/lexer.rb but modified to match also + # negative values and disallow numbers prefixed with multiple + # 0's + # + # TODO these parameter should be a constant but I'm not sure + # if there is no risk to declare it inside of the module + # Puppet::Parser::Functions + + # Integer numbers like + # -1234568981273 + # 47291 + numeric = %r{^-?(?:(?:[1-9]\d*)|0)$} + + if value.is_a? Integer or (value.is_a? String and value.match numeric) + return true + else + return false + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb new file mode 100644 index 0000000..79ddb98 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb @@ -0,0 +1,33 @@ +# +# is_ip_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IP address. + EOS + ) do |arguments| + + require 'ipaddr' + + function_deprecation([:is_ip_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_address. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ip_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + if ip.ipv4? or ip.ipv6? then + return true + else + return false + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb new file mode 100644 index 0000000..91869b6 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb @@ -0,0 +1,29 @@ +# +# is_ipv4_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ipv4_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IPv4 address. + EOS + ) do |arguments| + + require 'ipaddr' + + function_deprecation([:is_ipv4_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ipv4_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + return ip.ipv4? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb new file mode 100644 index 0000000..4d96202 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb @@ -0,0 +1,29 @@ +# +# is_ipv6_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ipv6_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IPv6 address. + EOS + ) do |arguments| + + function_deprecation([:is_ipv6_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6. There is further documentation for validate_legacy function in the README.']) + + require 'ipaddr' + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ipv6_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + return ip.ipv6? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb new file mode 100644 index 0000000..5993ed2 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb @@ -0,0 +1,26 @@ +# +# is_mac_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_mac_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid mac address. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_mac_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + mac = arguments[0] + + if /^[a-f0-9]{1,2}(:[a-f0-9]{1,2}){5}$/i.match(mac) then + return true + else + return false + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb new file mode 100644 index 0000000..7800edd --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb @@ -0,0 +1,74 @@ +# +# is_numeric.rb +# + +module Puppet::Parser::Functions + newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS +Returns true if the given argument is a Numeric (Integer or Float), +or a String containing either a valid integer in decimal base 10 form, or +a valid floating point string representation. + +The function recognizes only decimal (base 10) integers and float but not +integers in hex (base 16) or octal (base 8) form. + +The string representation may start with a '-' (minus). If a decimal '.' is used, +it must be followed by at least one digit. + +Valid examples: + + 77435 + 10e-12 + -8475 + 0.2343 + -23.561e3 + EOS + ) do |arguments| + + function_deprecation([:is_numeric, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_numeric(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Regex is taken from the lexer of puppet + # puppet/pops/parser/lexer.rb but modified to match also + # negative values and disallow invalid octal numbers or + # numbers prefixed with multiple 0's (except in hex numbers) + # + # TODO these parameters should be constants but I'm not sure + # if there is no risk to declare them inside of the module + # Puppet::Parser::Functions + + # TODO decide if this should be used + # HEX numbers like + # 0xaa230F + # 0X1234009C + # 0x0012 + # -12FcD + #numeric_hex = %r{^-?0[xX][0-9A-Fa-f]+$} + + # TODO decide if this should be used + # OCTAL numbers like + # 01234567 + # -045372 + #numeric_oct = %r{^-?0[1-7][0-7]*$} + + # Integer/Float numbers like + # -0.1234568981273 + # 47291 + # 42.12345e-12 + numeric = %r{^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$} + + if value.is_a? Numeric or (value.is_a? String and ( + value.match(numeric) #or + # value.match(numeric_hex) or + # value.match(numeric_oct) + )) + return true + else + return false + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_string.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_string.rb new file mode 100644 index 0000000..0ed6aec --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/is_string.rb @@ -0,0 +1,28 @@ +# +# is_string.rb +# + +module Puppet::Parser::Functions + newfunction(:is_string, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a string. + EOS + ) do |arguments| + + function_deprecation([:is_string, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_string(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + type = arguments[0] + + # when called through the v4 API shim, undef gets translated to nil + result = type.is_a?(String) || type.nil? + + if result and (type == type.to_f.to_s or type == type.to_i.to_s) then + return false + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/join.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/join.rb new file mode 100644 index 0000000..1c17997 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/join.rb @@ -0,0 +1,40 @@ +# +# join.rb +# + +module Puppet::Parser::Functions + newfunction(:join, :type => :rvalue, :doc => <<-EOS +This function joins an array into a string using a separator. + +*Examples:* + + join(['a','b','c'], ",") + +Would result in: "a,b,c" + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "join(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'join(): Requires array to work with') + end + + suffix = arguments[1] if arguments[1] + + if suffix + unless suffix.is_a?(String) + raise(Puppet::ParseError, 'join(): Requires string to work with') + end + end + + result = suffix ? array.join(suffix) : array.join + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb new file mode 100644 index 0000000..d8966ba --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb @@ -0,0 +1,53 @@ +# +# join.rb +# + +module Puppet::Parser::Functions + newfunction(:join_keys_to_values, :type => :rvalue, :doc => <<-EOS +This function joins each key of a hash to that key's corresponding value with a +separator. Keys are cast to strings. If values are arrays, multiple keys +are added for each element. The return value is an array in +which each element is one joined key/value pair. + +*Examples:* + + join_keys_to_values({'a'=>1,'b'=>2}, " is ") + +Would result in: ["a is 1","b is 2"] + + join_keys_to_values({'a'=>1,'b'=>[2,3]}, " is ") + +Would result in: ["a is 1","b is 2","b is 3"] + EOS + ) do |arguments| + + # Validate the number of arguments. + if arguments.size != 2 + raise(Puppet::ParseError, "join_keys_to_values(): Takes exactly two arguments, but #{arguments.size} given.") + end + + # Validate the first argument. + hash = arguments[0] + if not hash.is_a?(Hash) + raise(TypeError, "join_keys_to_values(): The first argument must be a hash, but a #{hash.class} was given.") + end + + # Validate the second argument. + separator = arguments[1] + if not separator.is_a?(String) + raise(TypeError, "join_keys_to_values(): The second argument must be a string, but a #{separator.class} was given.") + end + + # Join the keys to their values. + hash.map do |k,v| + if v.is_a?(Array) + v.map { |va| String(k) + separator + String(va) } + else + String(k) + separator + String(v) + end + end.flatten + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/keys.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/keys.rb new file mode 100644 index 0000000..199e319 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/keys.rb @@ -0,0 +1,25 @@ +# +# keys.rb +# + +module Puppet::Parser::Functions + newfunction(:keys, :type => :rvalue, :doc => <<-EOS +Returns the keys of a hash as an array. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "keys(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + hash = arguments[0] + + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'keys(): Requires hash to work with') + end + + result = hash.keys + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb new file mode 100644 index 0000000..c9b8488 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb @@ -0,0 +1,24 @@ +module Puppet::Parser::Functions + newfunction(:load_module_metadata, :type => :rvalue, :doc => <<-EOT + EOT + ) do |args| + raise(Puppet::ParseError, "load_module_metadata(): Wrong number of arguments, expects one or two") unless [1,2].include?(args.size) + mod = args[0] + allow_empty_metadata = args[1] + module_path = function_get_module_path([mod]) + metadata_json = File.join(module_path, 'metadata.json') + + metadata_exists = File.exists?(metadata_json) + if metadata_exists + metadata = PSON.load(File.read(metadata_json)) + else + if allow_empty_metadata + metadata = {} + else + raise(Puppet::ParseError, "load_module_metadata(): No metadata.json file for module #{mod}") + end + end + + return metadata + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadjson.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadjson.rb new file mode 100644 index 0000000..3a3372b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadjson.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:loadjson, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| +Load a JSON file containing an array, string, or hash, and return the data +in the corresponding native data type. +The second parameter is the default value. It will be returned if the file +was not found or could not be parsed. + +For example: + + $myhash = loadjson('/etc/puppet/data/myhash.json') + $myhash = loadjson('no-file.json', {'default' => 'value'}) + ENDHEREDOC + + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless args.length >= 1 + + if File.exists?(args[0]) + begin + content = File.read(args[0]) + PSON::load(content) || args[1] + rescue Exception => e + if args[1] + args[1] + else + raise e + end + end + else + warning("Can't load '#{args[0]}' File does not exist!") + args[1] + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb new file mode 100644 index 0000000..9696362 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:loadyaml, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| +Load a YAML file containing an array, string, or hash, and return the data +in the corresponding native data type. +The second parameter is the default value. It will be returned if the file +was not found or could not be parsed. + +For example: + + $myhash = loadyaml('/etc/puppet/data/myhash.yaml') + $myhash = loadyaml('no-file.yaml', {'default' => 'value'}) + ENDHEREDOC + + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless args.length >= 1 + require 'yaml' + + if File.exists?(args[0]) + begin + YAML::load_file(args[0]) || args[1] + rescue Exception => e + if args[1] + args[1] + else + raise e + end + end + else + warning("Can't load '#{args[0]}' File does not exist!") + args[1] + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/lstrip.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/lstrip.rb new file mode 100644 index 0000000..9a9ee29 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/lstrip.rb @@ -0,0 +1,30 @@ +# +# lstrip.rb +# + +module Puppet::Parser::Functions + newfunction(:lstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the left of a string. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "lstrip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'lstrip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.lstrip : i } + else + result = value.lstrip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/max.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/max.rb new file mode 100644 index 0000000..758c42c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/max.rb @@ -0,0 +1,20 @@ +module Puppet::Parser::Functions + newfunction(:max, :type => :rvalue, :doc => <<-EOS + Returns the highest value of all arguments. + Requires at least one argument. + EOS + ) do |args| + + raise(Puppet::ParseError, "max(): Wrong number of arguments need at least one") if args.size == 0 + + # Sometimes we get numbers as numerics and sometimes as strings. + # We try to compare them as numbers when possible + return args.max do |a,b| + if a.to_s =~ /\A-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then + a.to_f <=> b.to_f + else + a.to_s <=> b.to_s + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/member.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/member.rb new file mode 100644 index 0000000..fb93452 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/member.rb @@ -0,0 +1,60 @@ +# +# member.rb +# + +# TODO(Krzysztof Wilczynski): We need to add support for regular expression ... +# TODO(Krzysztof Wilczynski): Support for strings and hashes too ... + +module Puppet::Parser::Functions + newfunction(:member, :type => :rvalue, :doc => <<-EOS +This function determines if a variable is a member of an array. +The variable can be a string, fixnum, or array. + +*Examples:* + + member(['a','b'], 'b') + +Would return: true + + member(['a', 'b', 'c'], ['a', 'b']) + +would return: true + + member(['a','b'], 'c') + +Would return: false + + member(['a', 'b', 'c'], ['d', 'b']) + +would return: false + EOS + ) do |arguments| + + raise(Puppet::ParseError, "member(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'member(): Requires array to work with') + end + + unless arguments[1].is_a? String or arguments[1].is_a? Fixnum or arguments[1].is_a? Array + raise(Puppet::ParseError, 'member(): Item to search for must be a string, fixnum, or array') + end + + if arguments[1].is_a? String or arguments[1].is_a? Fixnum + item = [arguments[1]] + else + item = arguments[1] + end + + + raise(Puppet::ParseError, 'member(): You must provide item to search for within array given') if item.respond_to?('empty?') && item.empty? + + result = (item - array).empty? + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/merge.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/merge.rb new file mode 100644 index 0000000..1b39f20 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/merge.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two', => 2} + $hash2 = {'two' => 'dos', 'three', => 'tres'} + $merged_hash = merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'} + + When there is a duplicate key, the key in the rightmost hash will "win." + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("merge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + # The hash we accumulate into + accumulator = Hash.new + # Merge into the accumulator hash + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + unless arg.is_a?(Hash) + raise Puppet::ParseError, "merge: unexpected argument type #{arg.class}, only expects hash arguments" + end + accumulator.merge!(arg) + end + # Return the fully merged hash + accumulator + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/min.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/min.rb new file mode 100644 index 0000000..f10a2b2 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/min.rb @@ -0,0 +1,20 @@ +module Puppet::Parser::Functions + newfunction(:min, :type => :rvalue, :doc => <<-EOS + Returns the lowest value of all arguments. + Requires at least one argument. + EOS + ) do |args| + + raise(Puppet::ParseError, "min(): Wrong number of arguments need at least one") if args.size == 0 + + # Sometimes we get numbers as numerics and sometimes as strings. + # We try to compare them as numbers when possible + return args.min do |a,b| + if a.to_s =~ /\A^-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then + a.to_f <=> b.to_f + else + a.to_s <=> b.to_s + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/num2bool.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/num2bool.rb new file mode 100644 index 0000000..9ad59b2 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/num2bool.rb @@ -0,0 +1,42 @@ +# +# num2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS +This function converts a number or a string representation of a number into a +true boolean. Zero or anything non-numeric becomes false. Numbers higher then 0 +become true. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "num2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + number = arguments[0] + + case number + when Numeric + # Yay, it's a number + when String + begin + number = Float(number) + rescue ArgumentError => ex + raise(Puppet::ParseError, "num2bool(): '#{number}' does not look like a number: #{ex.message}") + end + else + begin + number = number.to_s + rescue NoMethodError => ex + raise(Puppet::ParseError, "num2bool(): Unable to parse argument: #{ex.message}") + end + end + + # Truncate Floats + number = number.to_i + + # Return true for any positive number and false otherwise + return number > 0 + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/parsejson.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/parsejson.rb new file mode 100644 index 0000000..f7c2896 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/parsejson.rb @@ -0,0 +1,29 @@ +# +# parsejson.rb +# + +module Puppet::Parser::Functions + newfunction(:parsejson, :type => :rvalue, :doc => <<-EOS +This function accepts JSON as a string and converts it into the correct +Puppet structure. + +The optional second argument can be used to pass a default value that will +be returned if the parsing of YAML string have failed. + EOS + ) do |arguments| + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless arguments.length >= 1 + + begin + PSON::load(arguments[0]) || arguments[1] + rescue StandardError => e + if arguments[1] + arguments[1] + else + raise e + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb new file mode 100644 index 0000000..ba9d98a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb @@ -0,0 +1,33 @@ +# +# parseyaml.rb +# + +module Puppet::Parser::Functions + newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS +This function accepts YAML as a string and converts it into the correct +Puppet structure. + +The optional second argument can be used to pass a default value that will +be returned if the parsing of YAML string have failed. + EOS + ) do |arguments| + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless arguments.length >= 1 + require 'yaml' + + begin + YAML::load(arguments[0]) || arguments[1] + # in ruby 1.9.3 Psych::SyntaxError is a RuntimeException + # this still needs to catch that and work also on rubies that + # do not have Psych available. + rescue StandardError, Psych::SyntaxError => e + if arguments[1] + arguments[1] + else + raise e + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick.rb new file mode 100644 index 0000000..fdd0aef --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick.rb @@ -0,0 +1,29 @@ +module Puppet::Parser::Functions + newfunction(:pick, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). Typically, +this function is used to check for a value in the Puppet Dashboard/Enterprise +Console, and failover to a default value like the following: + + $real_jenkins_version = pick($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +EOS +) do |args| + args = args.compact + args.delete(:undef) + args.delete(:undefined) + args.delete("") + if args[0].to_s.empty? then + fail Puppet::ParseError, "pick(): must receive at least one non empty value" + else + return args[0] + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick_default.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick_default.rb new file mode 100644 index 0000000..36e33ab --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pick_default.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + newfunction(:pick_default, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). If no value is +found, it will return the last argument. + +Typically, this function is used to check for a value in the Puppet +Dashboard/Enterprise Console, and failover to a default value like the +following: + + $real_jenkins_version = pick_default($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +Note that, contrary to the pick() function, the pick_default does not fail if +all arguments are empty. This allows pick_default to use an empty value as +default. + +EOS +) do |args| + fail "Must receive at least one argument." if args.empty? + default = args.last + args = args[0..-2].compact + args.delete(:undef) + args.delete(:undefined) + args.delete("") + args << default + return args[0] + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/prefix.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/prefix.rb new file mode 100644 index 0000000..b1c1e35 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/prefix.rb @@ -0,0 +1,51 @@ +# +# prefix.rb +# + +module Puppet::Parser::Functions + newfunction(:prefix, :type => :rvalue, :doc => <<-EOS +This function applies a prefix to all elements in an array or a hash. + +*Examples:* + + prefix(['a','b','c'], 'p') + +Will return: ['pa','pb','pc'] + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "prefix(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + enumerable = arguments[0] + + unless enumerable.is_a?(Array) or enumerable.is_a?(Hash) + raise Puppet::ParseError, "prefix(): expected first argument to be an Array or a Hash, got #{enumerable.inspect}" + end + + prefix = arguments[1] if arguments[1] + + if prefix + unless prefix.is_a?(String) + raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}" + end + end + + if enumerable.is_a?(Array) + # Turn everything into string same as join would do ... + result = enumerable.collect do |i| + i = i.to_s + prefix ? prefix + i : i + end + else + result = Hash[enumerable.map do |k,v| + k = k.to_s + [ prefix ? prefix + k : k, v ] + end] + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/private.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/private.rb new file mode 100644 index 0000000..3b00ba1 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/private.rb @@ -0,0 +1,17 @@ +# +# private.rb +# + +module Puppet::Parser::Functions + newfunction(:private, :doc => <<-'EOS' + DEPRECATED: Sets the current class or definition as private. + Calling the class or definition from outside the current module will fail. + EOS + ) do |args| + warning("private() DEPRECATED: This function will cease to function on Puppet 4; please use assert_private() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + if !Puppet::Parser::Functions.autoloader.loaded?(:assert_private) + Puppet::Parser::Functions.autoloader.load(:assert_private) + end + function_assert_private([(args[0] unless args.size < 1)]) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/pry.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pry.rb new file mode 100644 index 0000000..c18ef7e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pry.rb @@ -0,0 +1,30 @@ +# +# pry.rb +# + +module Puppet::Parser::Functions + newfunction(:pry, :type => :statement, :doc => <<-EOS +This function invokes a pry debugging session in the current scope object. This is useful for debugging manifest code at specific points during a compilation. + +*Examples:* + + pry() + EOS + ) do |arguments| + begin + require 'pry' + rescue LoadError + raise(Puppet::Error, "pry(): Requires the 'pry' rubygem to use, but it was not found") + end + # + ## Run `catalog` to see the contents currently compiling catalog + ## Run `cd catalog` and `ls` to see catalog methods and instance variables + ## Run `@resource_table` to see the current catalog resource table + # + if $stdout.isatty + binding.pry # rubocop:disable Lint/Debugger + else + Puppet.warning 'pry(): cowardly refusing to start the debugger on a daemonized master' + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb new file mode 100644 index 0000000..d99ee5b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb @@ -0,0 +1,59 @@ + +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +Puppet::Parser::Functions::newfunction( + :pw_hash, + :type => :rvalue, + :arity => 3, + :doc => "Hashes a password using the crypt function. Provides a hash + usable on most POSIX systems. + + The first argument to this function is the password to hash. If it is + undef or an empty string, this function returns undef. + + The second argument to this function is which type of hash to use. It + will be converted into the appropriate crypt(3) hash specifier. Valid + hash types are: + + |Hash type |Specifier| + |---------------------|---------| + |MD5 |1 | + |SHA-256 |5 | + |SHA-512 (recommended)|6 | + + The third argument to this function is the salt to use. + + Note: this uses the Puppet Master's implementation of crypt(3). If your + environment contains several different operating systems, ensure that they + are compatible before using this function.") do |args| + raise ArgumentError, "pw_hash(): wrong number of arguments (#{args.size} for 3)" if args.size != 3 + raise ArgumentError, "pw_hash(): first argument must be a string" unless args[0].is_a? String or args[0].nil? + raise ArgumentError, "pw_hash(): second argument must be a string" unless args[1].is_a? String + hashes = { 'md5' => '1', + 'sha-256' => '5', + 'sha-512' => '6' } + hash_type = hashes[args[1].downcase] + raise ArgumentError, "pw_hash(): #{args[1]} is not a valid hash type" if hash_type.nil? + raise ArgumentError, "pw_hash(): third argument must be a string" unless args[2].is_a? String + raise ArgumentError, "pw_hash(): third argument must not be empty" if args[2].empty? + raise ArgumentError, "pw_hash(): characters in salt must be in the set [a-zA-Z0-9./]" unless args[2].match(/\A[a-zA-Z0-9.\/]+\z/) + + password = args[0] + return nil if password.nil? or password.empty? + + salt = "$#{hash_type}$#{args[2]}" + + # handle weak implementations of String#crypt + if 'test'.crypt('$1$1') != '$1$1$Bp8CU9Oujr9SSEw53WV6G.' + # JRuby < 1.7.17 + if RUBY_PLATFORM == 'java' + # puppetserver bundles Apache Commons Codec + org.apache.commons.codec.digest.Crypt.crypt(password.to_java_bytes, salt) + else + # MS Windows and other systems that don't support enhanced salts + raise Puppet::ParseError, 'system does not support enhanced salts' + end + else + password.crypt(salt) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/range.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/range.rb new file mode 100644 index 0000000..72c373a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/range.rb @@ -0,0 +1,85 @@ +# +# range.rb +# + +# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ... + +module Puppet::Parser::Functions + newfunction(:range, :type => :rvalue, :doc => <<-EOS +When given range in the form of (start, stop) it will extrapolate a range as +an array. + +*Examples:* + + range("0", "9") + +Will return: [0,1,2,3,4,5,6,7,8,9] + + range("00", "09") + +Will return: [0,1,2,3,4,5,6,7,8,9] (Zero padded strings are converted to +integers automatically) + + range("a", "c") + +Will return: ["a","b","c"] + + range("host01", "host10") +Will return: ["host01", "host02", ..., "host09", "host10"] +NB Be explicit in including trailing zeros. Otherwise the underlying ruby function will fail. + +Passing a third argument will cause the generated range to step by that +interval, e.g. + + range("0", "9", "2") + +Will return: [0,2,4,6,8] + EOS + ) do |arguments| + + raise(Puppet::ParseError, 'range(): Wrong number of arguments given (0 for 1)') if arguments.size == 0 + + if arguments.size > 1 + start = arguments[0] + stop = arguments[1] + step = arguments[2].nil? ? 1 : arguments[2].to_i.abs + + type = '..' # Use the simplest type of Range available in Ruby + + else # arguments.size == 1 + value = arguments[0] + + if m = value.match(/^(\w+)(\.\.\.?|\-)(\w+)$/) + start = m[1] + stop = m[3] + + type = m[2] + step = 1 + elsif value.match(/^.+$/) + raise(Puppet::ParseError, "range(): Unable to compute range from the value: #{value}") + else + raise(Puppet::ParseError, "range(): Unknown range format: #{value}") + end + end + + # If we were given an integer, ensure we work with one + if start.to_s.match(/^\d+$/) + start = start.to_i + stop = stop.to_i + else + start = start.to_s + stop = stop.to_s + end + + range = case type + when /^(\.\.|\-)$/ then (start .. stop) + when '...' then (start ... stop) # Exclusive of last element + end + + result = range.step(step).to_a + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb new file mode 100644 index 0000000..2cfa3bb --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb @@ -0,0 +1,29 @@ +# +# regexpescape.rb +# +module Puppet::Parser::Functions + newfunction(:regexpescape, :type => :rvalue, :doc => <<-EOS + Regexp escape a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| # rubocop:disable Style/ClosingParenthesisIndentation + raise(Puppet::ParseError, "regexpescape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.empty? + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'regexpescape(): Requires either array or string to work with') + end + + result = if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + value.collect { |i| i.is_a?(String) ? Regexp.escape(i) : i } + else + Regexp.escape(value) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/reject.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/reject.rb new file mode 100644 index 0000000..1953ffc --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/reject.rb @@ -0,0 +1,31 @@ +# +# reject.rb +# + +module Puppet::Parser::Functions + newfunction(:reject, :type => :rvalue, :doc => <<-EOS) do |args| +This function searches through an array and rejects all elements that match +the provided regular expression. + +*Examples:* + + reject(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + + ['bbb','ccc'] +EOS + + if (args.size != 2) + raise Puppet::ParseError, + "reject(): Wrong number of arguments given #{args.size} for 2" + end + + ary = args[0] + pattern = Regexp.new(args[1]) + + ary.reject { |e| e =~ pattern } + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/reverse.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/reverse.rb new file mode 100644 index 0000000..aca98ce --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/reverse.rb @@ -0,0 +1,25 @@ +# +# reverse.rb +# + +module Puppet::Parser::Functions + newfunction(:reverse, :type => :rvalue, :doc => <<-EOS +Reverses the order of a string or array. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "reverse(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'reverse(): Requires either array or string to work with') + end + + result = value.reverse + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/rstrip.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/rstrip.rb new file mode 100644 index 0000000..e24abd5 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/rstrip.rb @@ -0,0 +1,29 @@ +# +# rstrip.rb +# + +module Puppet::Parser::Functions + newfunction(:rstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the right of the string. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "rstrip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'rstrip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + result = value.collect { |i| i.is_a?(String) ? i.rstrip : i } + else + result = value.rstrip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb new file mode 100644 index 0000000..44e27b8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb @@ -0,0 +1,22 @@ +Puppet::Parser::Functions::newfunction( + :seeded_rand, + :arity => 2, + :type => :rvalue, + :doc => <<-EOS +Usage: `seeded_rand(MAX, SEED)`. MAX must be a positive integer; SEED is any string. + +Generates a random whole number greater than or equal to 0 and less +than MAX, using the value of SEED for repeatable randomness. If SEED +starts with "$fqdn:", this is behaves the same as `fqdn_rand`. + +EOS +) do |args| + require 'digest/md5' + + raise(ArgumentError, "seeded_rand(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0 + raise(ArgumentError, "seeded_rand(): second argument must be a string") unless args[1].is_a? String + + max = args[0].to_i + seed = Digest::MD5.hexdigest(args[1]).hex + Puppet::Util.deterministic_rand(seed,max) +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb new file mode 100644 index 0000000..7306b7c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb @@ -0,0 +1,29 @@ +# +# shell_escape.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_escape, :type => :rvalue, :doc => <<-EOS +Escapes a string so that it can be safely used in a Bourne shell command line. + +Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single +quotes. + +This function behaves the same as ruby's Shellwords.shellescape() function. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_escape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + # explicit conversion to string is required for ruby 1.9 + string = arguments[0].to_s + + result = Shellwords.shellescape(string) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_join.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_join.rb new file mode 100644 index 0000000..682ed8d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_join.rb @@ -0,0 +1,30 @@ +# +# shell_join.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_join, :type => :rvalue, :doc => <<-EOS +Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are +then joined together, with a single space in between. + +This function behaves the same as ruby's Shellwords.shelljoin() function + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_join(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + array = arguments[0] + + raise Puppet::ParseError, ("First argument is not an Array: #{array.inspect}") unless array.is_a?(Array) + + # explicit conversion to string is required for ruby 1.9 + array = array.map { |item| item.to_s } + result = Shellwords.shelljoin(array) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_split.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_split.rb new file mode 100644 index 0000000..09e6e78 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shell_split.rb @@ -0,0 +1,25 @@ +# +# shell_split.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_split, :type => :rvalue, :doc => <<-EOS +Splits a string into an array of tokens in the same way the Bourne shell does. + +This function behaves the same as ruby's Shellwords.shellsplit() function + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_split(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + string = arguments[0].to_s + + result = Shellwords.shellsplit(string) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/shuffle.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shuffle.rb new file mode 100644 index 0000000..942cbce --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/shuffle.rb @@ -0,0 +1,43 @@ +# +# shuffle.rb +# + +module Puppet::Parser::Functions + newfunction(:shuffle, :type => :rvalue, :doc => <<-EOS +Randomizes the order of a string or array elements. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shuffle(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'shuffle(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # Check whether it makes sense to shuffle ... + return result if result.size <= 1 + + # We turn any string value into an array to be able to shuffle ... + result = string ? result.split('') : result + + elements = result.size + + # Simple implementation of Fisher–Yates in-place shuffle ... + elements.times do |i| + j = rand(elements - i) + i + result[j], result[i] = result[i], result[j] + end + + result = string ? result.join : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/size.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/size.rb new file mode 100644 index 0000000..b503aa0 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/size.rb @@ -0,0 +1,46 @@ +# +# size.rb +# + +module Puppet::Parser::Functions + newfunction(:size, :type => :rvalue, :doc => <<-EOS +Returns the number of elements in a string, an array or a hash + EOS + ) do |arguments| + + raise(Puppet::ParseError, "size(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + item = arguments[0] + + function_deprecation([:size, 'This method is going to be deprecated, please use the stdlib length function.']) + + if item.is_a?(String) + + begin + # + # Check whether your item is a numeric value or not ... + # This will take care about positive and/or negative numbers + # for both integer and floating-point values ... + # + # Please note that Puppet has no notion of hexadecimal + # nor octal numbers for its DSL at this point in time ... + # + Float(item) + + raise(Puppet::ParseError, 'size(): Requires either string, array or hash to work with') + + rescue ArgumentError + result = item.size + end + + elsif item.is_a?(Array) || item.is_a?(Hash) + result = item.size + else + raise(Puppet::ParseError, 'size(): Unknown type given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/sort.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/sort.rb new file mode 100644 index 0000000..d7792f5 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/sort.rb @@ -0,0 +1,27 @@ +# +# sort.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:sort, :type => :rvalue, :doc => <<-EOS +Sorts strings and arrays lexically. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "sort(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + if value.is_a?(Array) then + value.sort + elsif value.is_a?(String) then + value.split("").sort.join("") + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/squeeze.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/squeeze.rb new file mode 100644 index 0000000..f5757d1 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/squeeze.rb @@ -0,0 +1,35 @@ +# +# squeeze.rb +# + +module Puppet::Parser::Functions + newfunction(:squeeze, :type => :rvalue, :doc => <<-EOS +Returns a new string where runs of the same character that occur in this set are replaced by a single character. + EOS + ) do |arguments| + + if ((arguments.size != 2) and (arguments.size != 1)) then + raise(Puppet::ParseError, "squeeze(): Wrong number of arguments given #{arguments.size} for 2 or 1") + end + + item = arguments[0] + squeezeval = arguments[1] + + if item.is_a?(Array) then + if squeezeval then + item.collect { |i| i.squeeze(squeezeval) } + else + item.collect { |i| i.squeeze } + end + else + if squeezeval then + item.squeeze(squeezeval) + else + item.squeeze + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2bool.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2bool.rb new file mode 100644 index 0000000..38ad1ce --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2bool.rb @@ -0,0 +1,44 @@ +# +# str2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS +This converts a string to a boolean. This attempt to convert strings that +contain things like: Y,y, 1, T,t, TRUE,true to 'true' and strings that contain things +like: 0, F,f, N,n, false, FALSE, no to 'false'. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "str2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + string = arguments[0] + + # If string is already Boolean, return it + if !!string == string + return string + end + + unless string.is_a?(String) + raise(Puppet::ParseError, 'str2bool(): Requires string to work with') + end + + # We consider all the yes, no, y, n and so on too ... + result = case string + # + # This is how undef looks like in Puppet ... + # We yield false in this case. + # + when /^$/, '' then false # Empty string will be false ... + when /^(1|t|y|true|yes)$/i then true + when /^(0|f|n|false|no)$/i then false + when /^(undef|undefined)$/ then false # This is not likely to happen ... + else + raise(Puppet::ParseError, 'str2bool(): Unknown type of boolean given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb new file mode 100644 index 0000000..0410c62 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb @@ -0,0 +1,31 @@ +# +# str2saltedsha512.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:str2saltedsha512, :type => :rvalue, :doc => <<-EOS +This converts a string to a salted-SHA512 password hash (which is used for +OS X versions >= 10.7). Given any simple string, you will get a hex version +of a salted-SHA512 password hash that can be inserted into your Puppet +manifests as a valid password attribute. + EOS + ) do |arguments| + require 'digest/sha2' + + raise(Puppet::ParseError, "str2saltedsha512(): Wrong number of arguments passed (#{arguments.size} but we require 1)") if arguments.size != 1 + + password = arguments[0] + + unless password.is_a?(String) + raise(Puppet::ParseError, "str2saltedsha512(): Requires a String argument, you passed: #{password.class}") + end + + seedint = rand(2**31 - 1) + seedstring = Array(seedint).pack("L") + saltedpass = Digest::SHA512.digest(seedstring + password) + (seedstring + saltedpass).unpack('H*')[0] + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/strftime.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/strftime.rb new file mode 100644 index 0000000..deae329 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/strftime.rb @@ -0,0 +1,106 @@ +# +# strftime.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:strftime, :type => :rvalue, :doc => <<-EOS +This function returns formatted time. + +*Examples:* + +To return the time since epoch: + + strftime("%s") + +To return the date: + + strftime("%Y-%m-%d") + +*Format meaning:* + + %a - The abbreviated weekday name (``Sun'') + %A - The full weekday name (``Sunday'') + %b - The abbreviated month name (``Jan'') + %B - The full month name (``January'') + %c - The preferred local date and time representation + %C - Century (20 in 2009) + %d - Day of the month (01..31) + %D - Date (%m/%d/%y) + %e - Day of the month, blank-padded ( 1..31) + %F - Equivalent to %Y-%m-%d (the ISO 8601 date format) + %h - Equivalent to %b + %H - Hour of the day, 24-hour clock (00..23) + %I - Hour of the day, 12-hour clock (01..12) + %j - Day of the year (001..366) + %k - hour, 24-hour clock, blank-padded ( 0..23) + %l - hour, 12-hour clock, blank-padded ( 0..12) + %L - Millisecond of the second (000..999) + %m - Month of the year (01..12) + %M - Minute of the hour (00..59) + %n - Newline (\n) + %N - Fractional seconds digits, default is 9 digits (nanosecond) + %3N millisecond (3 digits) + %6N microsecond (6 digits) + %9N nanosecond (9 digits) + %p - Meridian indicator (``AM'' or ``PM'') + %P - Meridian indicator (``am'' or ``pm'') + %r - time, 12-hour (same as %I:%M:%S %p) + %R - time, 24-hour (%H:%M) + %s - Number of seconds since 1970-01-01 00:00:00 UTC. + %S - Second of the minute (00..60) + %t - Tab character (\t) + %T - time, 24-hour (%H:%M:%S) + %u - Day of the week as a decimal, Monday being 1. (1..7) + %U - Week number of the current year, + starting with the first Sunday as the first + day of the first week (00..53) + %v - VMS date (%e-%b-%Y) + %V - Week number of year according to ISO 8601 (01..53) + %W - Week number of the current year, + starting with the first Monday as the first + day of the first week (00..53) + %w - Day of the week (Sunday is 0, 0..6) + %x - Preferred representation for the date alone, no time + %X - Preferred representation for the time alone, no date + %y - Year without a century (00..99) + %Y - Year with century + %z - Time zone as hour offset from UTC (e.g. +0900) + %Z - Time zone name + %% - Literal ``%'' character + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "strftime(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + format = arguments[0] + + raise(Puppet::ParseError, 'strftime(): You must provide format for evaluation') if format.empty? + + # The Time Zone argument is optional ... + time_zone = arguments[1] if arguments[1] + + time = Time.new + + # There is probably a better way to handle Time Zone ... + if time_zone and not time_zone.empty? + original_zone = ENV['TZ'] + + local_time = time.clone + local_time = local_time.utc + + ENV['TZ'] = time_zone + + time = local_time.localtime + + ENV['TZ'] = original_zone + end + + result = time.strftime(format) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/strip.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/strip.rb new file mode 100644 index 0000000..9e8366f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/strip.rb @@ -0,0 +1,36 @@ +# +# strip.rb +# + +module Puppet::Parser::Functions + newfunction(:strip, :type => :rvalue, :doc => <<-EOS +This function removes leading and trailing whitespace from a string or from +every string inside an array. + +*Examples:* + + strip(" aaa ") + +Would result in: "aaa" + EOS + ) do |arguments| + + raise(Puppet::ParseError, "strip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'strip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + result = value.collect { |i| i.is_a?(String) ? i.strip : i } + else + result = value.strip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/suffix.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/suffix.rb new file mode 100644 index 0000000..7c5057d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/suffix.rb @@ -0,0 +1,52 @@ +# +# suffix.rb +# + +module Puppet::Parser::Functions + newfunction(:suffix, :type => :rvalue, :doc => <<-EOS +This function applies a suffix to all elements in an array, or to the keys +in a hash. + +*Examples:* + + suffix(['a','b','c'], 'p') + +Will return: ['ap','bp','cp'] + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "suffix(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + enumerable = arguments[0] + + unless enumerable.is_a?(Array) or enumerable.is_a?(Hash) + raise Puppet::ParseError, "suffix(): expected first argument to be an Array or a Hash, got #{enumerable.inspect}" + end + + suffix = arguments[1] if arguments[1] + + if suffix + unless suffix.is_a? String + raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}" + end + end + + if enumerable.is_a?(Array) + # Turn everything into string same as join would do ... + result = enumerable.collect do |i| + i = i.to_s + suffix ? i + suffix : i + end + else + result = Hash[enumerable.map do |k,v| + k = k.to_s + [ suffix ? k + suffix : k, v ] + end] + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/swapcase.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/swapcase.rb new file mode 100644 index 0000000..f3276fb --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/swapcase.rb @@ -0,0 +1,37 @@ +# +# swapcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:swapcase, :type => :rvalue, :doc => <<-EOS +This function will swap the existing case of a string. + +*Examples:* + + swapcase("aBcD") + +Would result in: "AbCd" + EOS + ) do |arguments| + + raise(Puppet::ParseError, "swapcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'swapcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.swapcase : i } + else + result = value.swapcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/time.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/time.rb new file mode 100644 index 0000000..d7780c8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/time.rb @@ -0,0 +1,49 @@ +# +# time.rb +# + +module Puppet::Parser::Functions + newfunction(:time, :type => :rvalue, :doc => <<-EOS +This function will return the current time since epoch as an integer. + +*Examples:* + + time() + +Will return something like: 1311972653 + EOS + ) do |arguments| + + # The Time Zone argument is optional ... + time_zone = arguments[0] if arguments[0] + + if (arguments.size != 0) and (arguments.size != 1) then + raise(Puppet::ParseError, "time(): Wrong number of arguments given #{arguments.size} for 0 or 1") + end + + time = Time.new + + # There is probably a better way to handle Time Zone ... + if time_zone and not time_zone.empty? + original_zone = ENV['TZ'] + + local_time = time.clone + local_time = local_time.utc + + ENV['TZ'] = time_zone + + result = local_time.localtime.strftime('%s') + + ENV['TZ'] = original_zone + else + result = time.localtime.strftime('%s') + end + + # Calling Time#to_i on a receiver changes it. Trust me I am the Doctor. + result = result.to_i + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb new file mode 100644 index 0000000..bf72503 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb @@ -0,0 +1,30 @@ +module Puppet::Parser::Functions + newfunction(:to_bytes, :type => :rvalue, :doc => <<-EOS + Converts the argument into bytes, for example 4 kB becomes 4096. + Takes a single string value as an argument. + These conversions reflect a layperson's understanding of + 1 MB = 1024 KB, when in fact 1 MB = 1000 KB, and 1 MiB = 1024 KiB. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "to_bytes(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + arg = arguments[0] + + return arg if arg.is_a? Numeric + + value,prefix = */([0-9.e+-]*)\s*([^bB]?)/.match(arg)[1,2] + + value = value.to_f + case prefix + when '' then return value.to_i + when 'k' then return (value*(1<<10)).to_i + when 'M' then return (value*(1<<20)).to_i + when 'G' then return (value*(1<<30)).to_i + when 'T' then return (value*(1<<40)).to_i + when 'P' then return (value*(1<<50)).to_i + when 'E' then return (value*(1<<60)).to_i + else raise Puppet::ParseError, "to_bytes(): Unknown prefix #{prefix}" + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb new file mode 100644 index 0000000..fc19a23 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb @@ -0,0 +1,53 @@ +module Puppet::Parser::Functions + newfunction( + :try_get_value, + :type => :rvalue, + :arity => -2, + :doc => <<-eos +DEPRECATED: this function is deprecated, please use dig() instead. + +Looks up into a complex structure of arrays and hashes and returns a value +or the default value if nothing was found. + +Key can contain slashes to describe path components. The function will go down +the structure and try to extract the required value. + +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = try_get_value($data, 'a/b/2', 'not_found', '/') +=> $value = 'b3' + +a -> first hash key +b -> second hash key +2 -> array index starting with 0 + +not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil. +/ -> (optional) path delimiter. Defaults to '/'. + +In addition to the required "key" argument, "try_get_value" accepts default +argument. It will be returned if no value was found or a path component is +missing. And the fourth argument can set a variable path separator. + eos + ) do |args| + warning("try_get_value() DEPRECATED: this function is deprecated, please use dig() instead.") + data = args[0] + path = args[1] || '' + default = args[2] + + if !(data.is_a?(Hash) || data.is_a?(Array)) || path == '' + return default || data + end + + separator = args[3] || '/' + path = path.split(separator).map{ |key| key =~ /^\d+$/ ? key.to_i : key } + function_dig([data, path, default]) + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/type.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/type.rb new file mode 100644 index 0000000..016529b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/type.rb @@ -0,0 +1,19 @@ +# +# type.rb +# + +module Puppet::Parser::Functions + newfunction(:type, :type => :rvalue, :doc => <<-EOS + DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system. + EOS + ) do |args| + + warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x) + Puppet::Parser::Functions.autoloader.load(:type3x) + end + function_type3x(args + [false]) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/type3x.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/type3x.rb new file mode 100644 index 0000000..b17380e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/type3x.rb @@ -0,0 +1,50 @@ +# +# type3x.rb +# + +module Puppet::Parser::Functions + newfunction(:type3x, :type => :rvalue, :doc => <<-EOS +DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system. + +Returns the type when passed a value. Type can be one of: + +* string +* array +* hash +* float +* integer +* boolean + EOS + ) do |args| + raise(Puppet::ParseError, "type3x(): Wrong number of arguments given (#{args.size} for 1)") if args.size < 1 + + value = args[0] + + klass = value.class + + if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass) + raise(Puppet::ParseError, 'type3x(): Unknown type') + end + + klass = klass.to_s # Ugly ... + + # We note that Integer is the parent to Bignum and Fixnum ... + result = case klass + when /^(?:Big|Fix)num$/ then 'integer' + when /^(?:True|False)Class$/ then 'boolean' + else klass + end + + if result == "String" then + if value == value.to_i.to_s then + result = "Integer" + elsif value == value.to_f.to_s then + result = "Float" + end + end + + return result.downcase + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/union.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/union.rb new file mode 100644 index 0000000..abe2dc8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/union.rb @@ -0,0 +1,28 @@ +# +# union.rb +# + +module Puppet::Parser::Functions + newfunction(:union, :type => :rvalue, :doc => <<-EOS +This function returns a union of two or more arrays. + +*Examples:* + + union(["a","b","c"],["b","c","d"]) + +Would return: ["a","b","c","d"] + EOS + ) do |arguments| + + # Check that 2 or more arguments have been given ... + raise(Puppet::ParseError, "union(): Wrong number of arguments given (#{arguments.size} for < 2)") if arguments.size < 2 + + arguments.each do |argument| + raise(Puppet::ParseError, 'union(): Every parameter must be an array') unless argument.is_a?(Array) + end + + arguments.reduce(:|) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/unique.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/unique.rb new file mode 100644 index 0000000..b57431d --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/unique.rb @@ -0,0 +1,48 @@ +# +# unique.rb +# + +module Puppet::Parser::Functions + newfunction(:unique, :type => :rvalue, :doc => <<-EOS +This function will remove duplicates from strings and arrays. + +*Examples:* + + unique("aabbcc") + +Will return: + + abc + +You can also use this with arrays: + + unique(["a","a","b","b","c","c"]) + +This returns: + + ["a","b","c"] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "unique(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'unique(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # We turn any string value into an array to be able to shuffle ... + result = string ? result.split('') : result + result = result.uniq # Remove duplicates ... + result = string ? result.join : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb new file mode 100644 index 0000000..0bd9cd1 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb @@ -0,0 +1,15 @@ +# Custom Puppet function to convert unix to dos format +module Puppet::Parser::Functions + newfunction(:unix2dos, :type => :rvalue, :arity => 1, :doc => <<-EOS + Returns the DOS version of the given string. + Takes a single string argument. + EOS + ) do |arguments| + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'unix2dos(): Requires string as argument') + end + + arguments[0].gsub(/\r*\n/, "\r\n") + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/upcase.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/upcase.rb new file mode 100644 index 0000000..e847e51 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/upcase.rb @@ -0,0 +1,44 @@ +# +# upcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:upcase, :type => :rvalue, :doc => <<-EOS +Converts a string or an array of strings to uppercase. + +*Examples:* + + upcase("abcd") + +Will return: + + ABCD + EOS + ) do |arguments| + + raise(Puppet::ParseError, "upcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(Hash) || value.respond_to?(:upcase) + raise(Puppet::ParseError, 'upcase(): Requires an array, hash or object that responds to upcase in order to work') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| function_upcase([i]) } + elsif value.is_a?(Hash) + result = {} + value.each_pair do |k, v| + result[function_upcase([k])] = function_upcase([v]) + end + else + result = value.upcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/uriescape.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/uriescape.rb new file mode 100644 index 0000000..4c5c400 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/uriescape.rb @@ -0,0 +1,33 @@ +# +# uriescape.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# +require 'uri' + +module Puppet::Parser::Functions + newfunction(:uriescape, :type => :rvalue, :doc => <<-EOS + Urlencodes a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "uriescape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'uriescape(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? URI.escape(i) : i } + else + result = URI.escape(value) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb new file mode 100644 index 0000000..c73f3df --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb @@ -0,0 +1,54 @@ +module Puppet::Parser::Functions + newfunction(:validate_absolute_path, :doc => <<-'ENDHEREDOC') do |args| + Validate the string represents an absolute path in the filesystem. This function works + for windows and unix style paths. + + The following values will pass: + + $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' + validate_absolute_path($my_path) + $my_path2 = '/var/lib/puppet' + validate_absolute_path($my_path2) + $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet'] + validate_absolute_path($my_path3) + $my_path4 = ['/var/lib/puppet','/usr/share/puppet'] + validate_absolute_path($my_path4) + + The following values will fail, causing compilation to abort: + + validate_absolute_path(true) + validate_absolute_path('../var/lib/puppet') + validate_absolute_path('var/lib/puppet') + validate_absolute_path([ 'var/lib/puppet', '/var/foo' ]) + validate_absolute_path([ '/var/lib/puppet', 'var/foo' ]) + $undefined = undef + validate_absolute_path($undefined) + + ENDHEREDOC + + # The deprecation function was being called twice, as validate_absolute_path calls is_absolute_path. I have removed it from here so it only calls deprecation once within is_absolute_path. + # function_deprecation([:validate_absolute_path, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_path. There is further documentation for validate_legacy function in the README.']) + + require 'puppet/util' + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_absolute_path(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + # put arg to candidate var to be able to replace it + candidates = arg + # if arg is just a string with a path to test, convert it to an array + # to avoid test code duplication + unless arg.is_a?(Array) then + candidates = Array.new(1,arg) + end + # iterate over all paths within the candidates array + candidates.each do |path| + unless function_is_absolute_path([path]) + raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.") + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_array.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_array.rb new file mode 100644 index 0000000..3bf3983 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_array.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:validate_array, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are array data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_array = [ 'one', 'two' ] + validate_array($my_array) + + The following values will fail, causing compilation to abort: + + validate_array(true) + validate_array('some_string') + $undefined = undef + validate_array($undefined) + + ENDHEREDOC + + function_deprecation([:validate_array, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_array(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(Array) + raise Puppet::ParseError, ("#{arg.inspect} is not an Array. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb new file mode 100644 index 0000000..2196c3e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb @@ -0,0 +1,83 @@ +require 'tempfile' + +module Puppet::Parser::Functions + newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args| + Perform validation of a string using an Augeas lens + The first argument of this function should be a string to + test, and the second argument should be the name of the Augeas lens to use. + If Augeas fails to parse the string with the lens, the compilation will + abort with a parse error. + + A third argument can be specified, listing paths which should + not be found in the file. The `$file` variable points to the location + of the temporary file being tested in the Augeas tree. + + For example, if you want to make sure your passwd content never contains + a user `foo`, you could write: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) + + Or if you wanted to ensure that no users used the '/bin/barsh' shell, + you could use: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] + + If a fourth argument is specified, this will be the error message raised and + seen by the user. + + A helpful error message can be returned like this: + + validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') + + ENDHEREDOC + unless Puppet.features.augeas? + raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://docs.puppetlabs.com/guides/augeas.html#pre-requisites for how to activate it.") + end + + if (args.length < 2) or (args.length > 4) then + raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)") + end + + msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}" + + require 'augeas' + aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) + begin + content = args[0] + + # Test content in a temporary file + tmpfile = Tempfile.new("validate_augeas") + begin + tmpfile.write(content) + ensure + tmpfile.close + end + + # Check for syntax + lens = args[1] + aug.transform( + :lens => lens, + :name => 'Validate_augeas', + :incl => tmpfile.path + ) + aug.load! + + unless aug.match("/augeas/files#{tmpfile.path}//error").empty? + error = aug.get("/augeas/files#{tmpfile.path}//error/message") + msg += " with error: #{error}" + raise Puppet::ParseError, (msg) + end + + # Launch unit tests + tests = args[2] || [] + aug.defvar('file', "/files#{tmpfile.path}") + tests.each do |t| + msg += " testing path #{t}" + raise Puppet::ParseError, (msg) unless aug.match(t).empty? + end + ensure + aug.close + tmpfile.unlink + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb new file mode 100644 index 0000000..49075b8 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb @@ -0,0 +1,37 @@ +module Puppet::Parser::Functions + + newfunction(:validate_bool, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are either true or false. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $iamtrue = true + validate_bool(true) + validate_bool(true, true, false, $iamtrue) + + The following values will fail, causing compilation to abort: + + $some_array = [ true ] + validate_bool("false") + validate_bool("true") + validate_bool($some_array) + + ENDHEREDOC + + # The deprecation function was being called twice, as validate_bool calls is_bool. I have removed it from here so it only calls deprecation once within is_bool. + # function_deprecation([:validate_bool, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_bool(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless function_is_bool([arg]) + raise Puppet::ParseError, ("#{arg.inspect} is not a boolean. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb new file mode 100644 index 0000000..685162b --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb @@ -0,0 +1,63 @@ +require 'puppet/util/execution' +require 'tempfile' + +module Puppet::Parser::Functions + newfunction(:validate_cmd, :doc => <<-'ENDHEREDOC') do |args| + Perform validation of a string with an external command. + The first argument of this function should be a string to + test, and the second argument should be a path to a test command + taking a % as a placeholder for the file path (will default to the end). + If the command, launched against a tempfile containing the passed string, + returns a non-null value, compilation will abort with a parse error. + + If a third argument is specified, this will be the error message raised and + seen by the user. + + A helpful error message can be returned like this: + + Example: + + # Defaults to end of path + validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') + + # % as file location + validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content') + + ENDHEREDOC + if (args.length < 2) or (args.length > 3) then + raise Puppet::ParseError, ("validate_cmd(): wrong number of arguments (#{args.length}; must be 2 or 3)") + end + + msg = args[2] || "validate_cmd(): failed to validate content with command #{args[1].inspect}" + + content = args[0] + checkscript = args[1] + + # Test content in a temporary file + tmpfile = Tempfile.new("validate_cmd") + begin + tmpfile.write(content) + tmpfile.close + + if checkscript =~ /\s%(\s|$)/ + check_with_correct_location = checkscript.gsub(/%/,tmpfile.path) + else + check_with_correct_location = "#{checkscript} #{tmpfile.path}" + end + + if Puppet::Util::Execution.respond_to?('execute') + Puppet::Util::Execution.execute(check_with_correct_location) + else + Puppet::Util.execute(check_with_correct_location) + end + rescue Puppet::ExecutionFailure => detail + msg += "\n#{detail}" + raise Puppet::ParseError, msg + rescue StandardError => detail + msg += "\n#{detail.class.name} #{detail}" + raise Puppet::ParseError, msg + ensure + tmpfile.unlink + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb new file mode 100644 index 0000000..ddd0d25 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb @@ -0,0 +1,31 @@ +module Puppet::Parser::Functions + newfunction(:validate_email_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid email addresses. + Fail compilation if any value fails this check. + The following values will pass: + $my_email = "waldo@gmail.com" + validate_email_address($my_email) + validate_email_address("bob@gmail.com", "alice@gmail.com", $my_email) + + The following values will fail, causing compilation to abort: + $some_array = [ 'bad_email@/d/efdf.com' ] + validate_email_address($some_array) + ENDHEREDOC + ) do |args| + rescuable_exceptions = [ArgumentError] + + if args.empty? + raise Puppet::ParseError, "validate_email_address(): wrong number of arguments (#{args.length}; must be > 0)" + end + + args.each do |arg| + raise Puppet::ParseError, "#{arg.inspect} is not a string." unless arg.is_a?(String) + + begin + raise Puppet::ParseError, "#{arg.inspect} is not a valid email address" unless function_is_email_address([arg]) + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid email address" + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb new file mode 100644 index 0000000..fcdc7e1 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:validate_hash, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are hash data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_hash = { 'one' => 'two' } + validate_hash($my_hash) + + The following values will fail, causing compilation to abort: + + validate_hash(true) + validate_hash('some_string') + $undefined = undef + validate_hash($undefined) + + ENDHEREDOC + + function_deprecation([:validate_hash, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Hash. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_hash(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(Hash) + raise Puppet::ParseError, ("#{arg.inspect} is not a Hash. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb new file mode 100644 index 0000000..2ae0293 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb @@ -0,0 +1,134 @@ +module Puppet::Parser::Functions + + newfunction(:validate_integer, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is an integer (or an array of integers). Abort catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not an integer or array of integers, and if arg 2 and arg 3 are not convertable to an integer. + + The following values will pass: + + validate_integer(1) + validate_integer(1, 2) + validate_integer(1, 1) + validate_integer(1, 2, 0) + validate_integer(2, 2, 2) + validate_integer(2, '', 0) + validate_integer(2, undef, 0) + $foo = undef + validate_integer(2, $foo, 0) + validate_integer([1,2,3,4,5], 6) + validate_integer([1,2,3,4,5], 6, 0) + + Plus all of the above, but any combination of values passed as strings ('1' or "1"). + Plus all of the above, but with (correct) combinations of negative integer values. + + The following values will not: + + validate_integer(true) + validate_integer(false) + validate_integer(7.0) + validate_integer({ 1 => 2 }) + $foo = undef + validate_integer($foo) + validate_integer($foobaridontexist) + + validate_integer(1, 0) + validate_integer(1, true) + validate_integer(1, '') + validate_integer(1, undef) + validate_integer(1, , 0) + validate_integer(1, 2, 3) + validate_integer(1, 3, 2) + validate_integer(1, 3, true) + + Plus all of the above, but any combination of values passed as strings ('false' or "false"). + Plus all of the above, but with incorrect combinations of negative integer values. + Plus all of the above, but with non-integer items in arrays or maximum / minimum argument. + + ENDHEREDOC + + function_deprecation([:validate_integer, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.']) + + # tell the user we need at least one, and optionally up to two other parameters + raise Puppet::ParseError, "validate_integer(): Wrong number of arguments; must be 1, 2 or 3, got #{args.length}" unless args.length > 0 and args.length < 4 + + input, max, min = *args + + # check maximum parameter + if args.length > 1 + max = max.to_s + # allow max to be empty (or undefined) if we have a minimum set + if args.length > 2 and max == '' + max = nil + else + begin + max = Integer(max) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected second argument to be unset or an Integer, got #{max}:#{max.class}" + end + end + else + max = nil + end + + # check minimum parameter + if args.length > 2 + begin + min = Integer(min.to_s) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected third argument to be unset or an Integer, got #{min}:#{min.class}" + end + else + min = nil + end + + # ensure that min < max + if min and max and min > max + raise Puppet::ParseError, "validate_integer(): Expected second argument to be larger than third argument, got #{max} < #{min}" + end + + # create lamba validator function + validator = lambda do |num| + # check input < max + if max and num > max + raise Puppet::ParseError, "validate_integer(): Expected #{input.inspect} to be smaller or equal to #{max}, got #{input.inspect}." + end + # check input > min (this will only be checked if no exception has been raised before) + if min and num < min + raise Puppet::ParseError, "validate_integer(): Expected #{input.inspect} to be greater or equal to #{min}, got #{input.inspect}." + end + end + + # if this is an array, handle it. + case input + when Array + # check every element of the array + input.each_with_index do |arg, pos| + begin + raise TypeError if arg.is_a?(Hash) + arg = Integer(arg.to_s) + validator.call(arg) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected element at array position #{pos} to be an Integer, got #{arg.class}" + end + end + # for the sake of compatibility with ruby 1.8, we need extra handling of hashes + when Hash + raise Puppet::ParseError, "validate_integer(): Expected first argument to be an Integer or Array, got #{input.class}" + # check the input. this will also fail any stuff other than pure, shiny integers + else + begin + input = Integer(input.to_s) + validator.call(input) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected first argument to be an Integer or Array, got #{input.class}" + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb new file mode 100644 index 0000000..5d80cfb --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb @@ -0,0 +1,52 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ip_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IP addresses, + regardless they are IPv4 or IPv6 + Fail compilation if any value fails this check. + The following values will pass: + $my_ip = "1.2.3.4" + validate_ip_address($my_ip) + validate_ip_address("8.8.8.8", "172.16.0.1", $my_ip) + + $my_ip = "3ffe:505:2" + validate_ip_address(1) + validate_ip_address($my_ip) + validate_ip_address("fe80::baf6:b1ff:fe19:7507", $my_ip) + + The following values will fail, causing compilation to abort: + $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] + validate_ip_address($some_array) + ENDHEREDOC + ) do |args| + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + function_deprecation([:validate_ip_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_address. There is further documentation for validate_legacy function in the README.']) + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ip_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv4? or IPAddr.new(arg).ipv6? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IP address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IP address." + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb new file mode 100644 index 0000000..0660abd --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv4 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "1.2.3.4" + validate_ipv4_address($my_ip) + validate_ipv4_address("8.8.8.8", "172.16.0.1", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] + validate_ipv4_address($some_array) + + ENDHEREDOC + ) do |args| + + function_deprecation([:validate_ipv4_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4. There is further documentation for validate_legacy function in the README.']) + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv4? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb new file mode 100644 index 0000000..f5dd9e5 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb @@ -0,0 +1,51 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv6 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "3ffe:505:2" + validate_ipv6_address(1) + validate_ipv6_address($my_ip) + validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ true, false, "garbage string", "1.2.3.4" ] + validate_ipv6_address($some_array) + + ENDHEREDOC + ) do |args| + + function_deprecation([:validate_ipv6_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6. There is further documentation for validate_legacy function in the README.']) + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv6? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb new file mode 100644 index 0000000..4205b30 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb @@ -0,0 +1,96 @@ +module Puppet::Parser::Functions + + newfunction(:validate_numeric, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is a numeric value (or an array of numeric values). Abort catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not a numeric (Integer or Float) or array of numerics, and if arg 2 and arg 3 are not convertable to a numeric. + + For passing and failing usage, see `validate_integer()`. It is all the same for validate_numeric, yet now floating point values are allowed, too. + + ENDHEREDOC + + function_deprecation([:validate_numeric, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.']) + + # tell the user we need at least one, and optionally up to two other parameters + raise Puppet::ParseError, "validate_numeric(): Wrong number of arguments; must be 1, 2 or 3, got #{args.length}" unless args.length > 0 and args.length < 4 + + input, max, min = *args + + # check maximum parameter + if args.length > 1 + max = max.to_s + # allow max to be empty (or undefined) if we have a minimum set + if args.length > 2 and max == '' + max = nil + else + begin + max = Float(max) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected second argument to be unset or a Numeric, got #{max}:#{max.class}" + end + end + else + max = nil + end + + # check minimum parameter + if args.length > 2 + begin + min = Float(min.to_s) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected third argument to be unset or a Numeric, got #{min}:#{min.class}" + end + else + min = nil + end + + # ensure that min < max + if min and max and min > max + raise Puppet::ParseError, "validate_numeric(): Expected second argument to be larger than third argument, got #{max} < #{min}" + end + + # create lamba validator function + validator = lambda do |num| + # check input < max + if max and num > max + raise Puppet::ParseError, "validate_numeric(): Expected #{input.inspect} to be smaller or equal to #{max}, got #{input.inspect}." + end + # check input > min (this will only be checked if no exception has been raised before) + if min and num < min + raise Puppet::ParseError, "validate_numeric(): Expected #{input.inspect} to be greater or equal to #{min}, got #{input.inspect}." + end + end + + # if this is an array, handle it. + case input + when Array + # check every element of the array + input.each_with_index do |arg, pos| + begin + raise TypeError if arg.is_a?(Hash) + arg = Float(arg.to_s) + validator.call(arg) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected element at array position #{pos} to be a Numeric, got #{arg.class}" + end + end + # for the sake of compatibility with ruby 1.8, we need extra handling of hashes + when Hash + raise Puppet::ParseError, "validate_integer(): Expected first argument to be a Numeric or Array, got #{input.class}" + # check the input. this will also fail any stuff other than pure, shiny integers + else + begin + input = Float(input.to_s) + validator.call(input) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected first argument to be a Numeric or Array, got #{input.class}" + end + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_re.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_re.rb new file mode 100644 index 0000000..0ac83dd --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_re.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + newfunction(:validate_re, :doc => <<-'ENDHEREDOC') do |args| + Perform simple validation of a string against one or more regular + expressions. The first argument of this function should be a string to + test, and the second argument should be a stringified regular expression + (without the // delimiters) or an array of regular expressions. If none + of the regular expressions match the string passed in, compilation will + abort with a parse error. + + If a third argument is specified, this will be the error message raised and + seen by the user. + + The following strings will validate against the regular expressions: + + validate_re('one', '^one$') + validate_re('one', [ '^one', '^two' ]) + + The following strings will fail to validate, causing compilation to abort: + + validate_re('one', [ '^two', '^three' ]) + + A helpful error message can be returned like this: + + validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7') + + Note: Compilation will also abort, if the first argument is not a String. Always use + quotes to force stringification: + + validate_re("${::operatingsystemmajrelease}", '^[57]$') + + ENDHEREDOC + + function_deprecation([:validate_re, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Re. There is further documentation for validate_legacy function in the README.']) + + if (args.length < 2) or (args.length > 3) then + raise Puppet::ParseError, "validate_re(): wrong number of arguments (#{args.length}; must be 2 or 3)" + end + + raise Puppet::ParseError, "validate_re(): input needs to be a String, not a #{args[0].class}" unless args[0].is_a? String + + msg = args[2] || "validate_re(): #{args[0].inspect} does not match #{args[1].inspect}" + + # We're using a flattened array here because we can't call String#any? in + # Ruby 1.9 like we can in Ruby 1.8 + raise Puppet::ParseError, msg unless [args[1]].flatten.any? do |re_str| + args[0] =~ Regexp.compile(re_str) + end + + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb new file mode 100644 index 0000000..383855c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb @@ -0,0 +1,71 @@ +module Puppet::Parser::Functions + + newfunction(:validate_slength, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is a string (or an array of strings), and + less/equal to than the length of the second argument. An optional third + parameter can be given the minimum length. It fails if the first + argument is not a string or array of strings, and if arg 2 and arg 3 are + not convertable to a number. + + The following values will pass: + + validate_slength("discombobulate",17) + validate_slength(["discombobulate","moo"],17) + validate_slength(["discombobulate","moo"],17,3) + + The following valueis will not: + + validate_slength("discombobulate",1) + validate_slength(["discombobulate","thermometer"],5) + validate_slength(["discombobulate","moo"],17,10) + + ENDHEREDOC + + function_deprecation([:validate_slength, 'This method is deprecated, please use the stdlib validate_legacy function, with String[]. There is further documentation for validate_legacy function in the README.']) + + raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3 + + input, max_length, min_length = *args + + begin + max_length = Integer(max_length) + raise ArgumentError if max_length <= 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}" + end + + if min_length + begin + min_length = Integer(min_length) + raise ArgumentError if min_length < 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}" + end + else + min_length = 0 + end + + raise Puppet::ParseError, "validate_slength(): Expected second argument to be equal to or larger than third argument" unless max_length >= min_length + + validator = lambda do |str| + unless str.length <= max_length and str.length >= min_length + raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}" + end + end + + case input + when String + validator.call(input) + when Array + input.each_with_index do |arg, pos| + if arg.is_a? String + validator.call(arg) + else + raise Puppet::ParseError, "validate_slength(): Expected element at array position #{pos} to be a String, got #{arg.class}" + end + end + else + raise Puppet::ParseError, "validate_slength(): Expected first argument to be a String or Array, got #{input.class}" + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_string.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_string.rb new file mode 100644 index 0000000..6675d86 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_string.rb @@ -0,0 +1,41 @@ +module Puppet::Parser::Functions + + newfunction(:validate_string, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are string data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_string = "one two" + validate_string($my_string, 'three') + + The following values will fail, causing compilation to abort: + + validate_string(true) + validate_string([ 'some', 'array' ]) + + Note: validate_string(undef) will not fail in this version of the + functions API (incl. current and future parser). Instead, use: + + if $var == undef { + fail('...') + } + + ENDHEREDOC + + function_deprecation([:validate_string, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_string(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + # when called through the v4 API shim, undef gets translated to nil + unless arg.is_a?(String) || arg.nil? + raise Puppet::ParseError, ("#{arg.inspect} is not a string. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb new file mode 100644 index 0000000..fc9f23f --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb @@ -0,0 +1,47 @@ +module Puppet::Parser::Functions + + newfunction(:validate_x509_rsa_key_pair, :doc => <<-ENDHEREDOC + Validates a PEM-formatted X.509 certificate and RSA private key using + OpenSSL. Verifies that the certficate's signature was created from the + supplied key. + + Fail compilation if any value fails this check. + + validate_x509_rsa_key_pair($cert, $key) + + ENDHEREDOC + ) do |args| + + require 'openssl' + + NUM_ARGS = 2 unless defined? NUM_ARGS + + unless args.length == NUM_ARGS then + raise Puppet::ParseError, + ("validate_x509_rsa_key_pair(): wrong number of arguments (#{args.length}; must be #{NUM_ARGS})") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + end + + begin + cert = OpenSSL::X509::Certificate.new(args[0]) + rescue OpenSSL::X509::CertificateError => e + raise Puppet::ParseError, "Not a valid x509 certificate: #{e}" + end + + begin + key = OpenSSL::PKey::RSA.new(args[1]) + rescue OpenSSL::PKey::RSAError => e + raise Puppet::ParseError, "Not a valid RSA key: #{e}" + end + + unless cert.verify(key) + raise Puppet::ParseError, "Certificate signature does not match supplied key" + end + end + +end diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/values.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/values.rb new file mode 100644 index 0000000..0ca236c --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/values.rb @@ -0,0 +1,38 @@ +# +# values.rb +# + +module Puppet::Parser::Functions + newfunction(:values, :type => :rvalue, :doc => <<-EOS +When given a hash this function will return the values of that hash. + +*Examples:* + + $hash = { + 'a' => 1, + 'b' => 2, + 'c' => 3, + } + values($hash) + +This example would return: + + [1,2,3] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "values(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + hash = arguments[0] + + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'values(): Requires hash to work with') + end + + result = hash.values + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/values_at.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/values_at.rb new file mode 100644 index 0000000..04a3d1a --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/values_at.rb @@ -0,0 +1,93 @@ +# +# values_at.rb +# + +module Puppet::Parser::Functions + newfunction(:values_at, :type => :rvalue, :doc => <<-EOS +Finds value inside an array based on location. + +The first argument is the array you want to analyze, and the second element can +be a combination of: + +* A single numeric index +* A range in the form of 'start-stop' (eg. 4-9) +* An array combining the above + +*Examples*: + + values_at(['a','b','c'], 2) + +Would return ['c']. + + values_at(['a','b','c'], ["0-1"]) + +Would return ['a','b']. + + values_at(['a','b','c','d','e'], [0, "2-3"]) + +Would return ['a','c','d']. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "values_at(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments.shift + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'values_at(): Requires array to work with') + end + + indices = [arguments.shift].flatten() # Get them all ... Pokemon ... + + if not indices or indices.empty? + raise(Puppet::ParseError, 'values_at(): You must provide at least one positive index to collect') + end + + result = [] + indices_list = [] + + indices.each do |i| + i = i.to_s + if m = i.match(/^(\d+)(\.\.\.?|\-)(\d+)$/) + start = m[1].to_i + stop = m[3].to_i + + type = m[2] + + if start > stop + raise(Puppet::ParseError, 'values_at(): Stop index in given indices range is smaller than the start index') + elsif stop > array.size - 1 # First element is at index 0 is it not? + raise(Puppet::ParseError, 'values_at(): Stop index in given indices range exceeds array size') + end + + range = case type + when /^(\.\.|\-)$/ then (start .. stop) + when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ... + end + + range.each { |i| indices_list << i.to_i } + else + # Only positive numbers allowed in this case ... + if not i.match(/^\d+$/) + raise(Puppet::ParseError, 'values_at(): Unknown format of given index') + end + + # In Puppet numbers are often string-encoded ... + i = i.to_i + + if i > array.size - 1 # Same story. First element is at index 0 ... + raise(Puppet::ParseError, 'values_at(): Given index exceeds array size') + end + + indices_list << i + end + end + + # We remove nil values as they make no sense in Puppet DSL ... + result = indices_list.collect { |i| array[i] }.compact + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/parser/functions/zip.rb b/environments/prod/modules/stdlib/lib/puppet/parser/functions/zip.rb new file mode 100644 index 0000000..13e24b6 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/parser/functions/zip.rb @@ -0,0 +1,38 @@ +# +# zip.rb +# + +module Puppet::Parser::Functions + newfunction(:zip, :type => :rvalue, :doc => <<-EOS +Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments. + +*Example:* + + zip(['1','2','3'],['4','5','6']) + +Would result in: + + ["1", "4"], ["2", "5"], ["3", "6"] + EOS + ) do |arguments| + + # Technically we support three arguments but only first is mandatory ... + raise(Puppet::ParseError, "zip(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + a = arguments[0] + b = arguments[1] + + unless a.is_a?(Array) and b.is_a?(Array) + raise(Puppet::ParseError, 'zip(): Requires array to work with') + end + + flatten = function_str2bool([arguments[2]]) if arguments[2] + + result = a.zip(b) + result = flatten ? result.flatten : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/prod/modules/stdlib/lib/puppet/provider/file_line/ruby.rb b/environments/prod/modules/stdlib/lib/puppet/provider/file_line/ruby.rb new file mode 100644 index 0000000..fbf7d8e --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/provider/file_line/ruby.rb @@ -0,0 +1,132 @@ +Puppet::Type.type(:file_line).provide(:ruby) do + def exists? + if resource[:replace].to_s != 'true' and count_matches(match_regex) > 0 + true + else + lines.find do |line| + if resource[:ensure].to_s == 'absent' and resource[:match_for_absence].to_s == 'true' + line.chomp =~ Regexp.new(resource[:match]) + else + line.chomp == resource[:line].chomp + end + end + end + end + + def create + unless resource[:replace].to_s != 'true' and count_matches(match_regex) > 0 + if resource[:match] + handle_create_with_match + elsif resource[:after] + handle_create_with_after + else + append_line + end + end + end + + def destroy + if resource[:match_for_absence].to_s == 'true' and resource[:match] + handle_destroy_with_match + else + handle_destroy_line + end + end + + private + def lines + # If this type is ever used with very large files, we should + # write this in a different way, using a temp + # file; for now assuming that this type is only used on + # small-ish config files that can fit into memory without + # too much trouble. + @lines ||= File.readlines(resource[:path], :encoding => resource[:encoding]) + end + + def match_regex + resource[:match] ? Regexp.new(resource[:match]) : nil + end + + def handle_create_with_match() + regex_after = resource[:after] ? Regexp.new(resource[:after]) : nil + match_count = count_matches(match_regex) + + if match_count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + end + + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(match_regex.match(l) ? resource[:line] : l) + if (match_count == 0 and regex_after) + if regex_after.match(l) + fh.puts(resource[:line]) + match_count += 1 #Increment match_count to indicate that the new line has been inserted. + end + end + end + + if (match_count == 0) + fh.puts(resource[:line]) + end + end + end + + def handle_create_with_after + regex = Regexp.new(resource[:after]) + count = count_matches(regex) + + if count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'. One or no line must match the pattern." + end + + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(l) + if regex.match(l) then + fh.puts(resource[:line]) + end + end + end + + if (count == 0) # append the line to the end of the file + append_line + end + end + + def count_matches(regex) + lines.select{|l| l.match(regex)}.size + end + + def handle_destroy_with_match + match_count = count_matches(match_regex) + if match_count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + end + + local_lines = lines + File.open(resource[:path],'w') do |fh| + fh.write(local_lines.reject{|l| match_regex.match(l) }.join('')) + end + end + + def handle_destroy_line + local_lines = lines + File.open(resource[:path],'w') do |fh| + fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join('')) + end + end + + ## + # append the line to the file. + # + # @api private + def append_line + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(l) + end + fh.puts resource[:line] + end + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/type/anchor.rb b/environments/prod/modules/stdlib/lib/puppet/type/anchor.rb new file mode 100644 index 0000000..fe1e5aa --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/type/anchor.rb @@ -0,0 +1,46 @@ +Puppet::Type.newtype(:anchor) do + desc <<-'ENDOFDESC' + A simple resource type intended to be used as an anchor in a composite class. + + In Puppet 2.6, when a class declares another class, the resources in the + interior class are not contained by the exterior class. This interacts badly + with the pattern of composing complex modules from smaller classes, as it + makes it impossible for end users to specify order relationships between the + exterior class and other modules. + + The anchor type lets you work around this. By sandwiching any interior + classes between two no-op resources that _are_ contained by the exterior + class, you can ensure that all resources in the module are contained. + + class ntp { + # These classes will have the correct order relationship with each + # other. However, without anchors, they won't have any order + # relationship to Class['ntp']. + class { 'ntp::package': } + -> class { 'ntp::config': } + -> class { 'ntp::service': } + + # These two resources "anchor" the composed classes within the ntp + # class. + anchor { 'ntp::begin': } -> Class['ntp::package'] + Class['ntp::service'] -> anchor { 'ntp::end': } + } + + This allows the end user of the ntp module to establish require and before + relationships with Class['ntp']: + + class { 'ntp': } -> class { 'mcollective': } + class { 'mcollective': } -> class { 'ntp': } + + ENDOFDESC + + newparam :name do + desc "The name of the anchor resource." + end + + def refresh + # We don't do anything with them, but we need this to + # show that we are "refresh aware" and not break the + # chain of propagation. + end +end diff --git a/environments/prod/modules/stdlib/lib/puppet/type/file_line.rb b/environments/prod/modules/stdlib/lib/puppet/type/file_line.rb new file mode 100644 index 0000000..e82b246 --- /dev/null +++ b/environments/prod/modules/stdlib/lib/puppet/type/file_line.rb @@ -0,0 +1,146 @@ +Puppet::Type.newtype(:file_line) do + + desc <<-EOT + Ensures that a given line is contained within a file. The implementation + matches the full line, including whitespace at the beginning and end. If + the line is not contained in the given file, Puppet will append the line to + the end of the file to ensure the desired state. Multiple resources may + be declared to manage multiple lines in the same file. + + Example: + + file_line { 'sudo_rule': + path => '/etc/sudoers', + line => '%sudo ALL=(ALL) ALL', + } + + file_line { 'sudo_rule_nopw': + path => '/etc/sudoers', + line => '%sudonopw ALL=(ALL) NOPASSWD: ALL', + } + + In this example, Puppet will ensure both of the specified lines are + contained in the file /etc/sudoers. + + Match Example: + + file_line { 'bashrc_proxy': + ensure => present, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + } + + In this code example match will look for a line beginning with export + followed by HTTP_PROXY and replace it with the value in line. + + Match Example With `ensure => absent`: + + file_line { 'bashrc_proxy': + ensure => absent, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + match_for_absence => true, + } + + In this code example match will look for a line beginning with export + followed by HTTP_PROXY and delete it. If multiple lines match, an + error will be raised unless the `multiple => true` parameter is set. + + Encoding example: + + file_line { "XScreenSaver": + ensure => present, + path => '/root/XScreenSaver' + line => "*lock: 10:00:00", + match => '^*lock:', + encoding => "iso-8859-1", + } + + Files with special characters that are not valid UTF-8 will give the + error message "invalid byte sequence in UTF-8". In this case, determine + the correct file encoding and specify the correct encoding using the + encoding attribute, the value of which needs to be a valid Ruby character + encoding. + + **Autorequires:** If Puppet is managing the file that will contain the line + being managed, the file_line resource will autorequire that file. + EOT + + ensurable do + defaultvalues + defaultto :present + end + + newparam(:name, :namevar => true) do + desc 'An arbitrary name used as the identity of the resource.' + end + + newparam(:match) do + desc 'An optional ruby regular expression to run against existing lines in the file.' + + ' If a match is found, we replace that line rather than adding a new line.' + + ' A regex comparison is performed against the line value and if it does not' + + ' match an exception will be raised.' + end + + newparam(:match_for_absence) do + desc 'An optional value to determine if match should be applied when ensure => absent.' + + ' If set to true and match is set, the line that matches match will be deleted.' + + ' If set to false (the default), match is ignored when ensure => absent.' + + ' When `ensure => present`, match_for_absence is ignored.' + newvalues(true, false) + defaultto false + end + + newparam(:multiple) do + desc 'An optional value to determine if match can change multiple lines.' + + ' If set to false, an exception will be raised if more than one line matches' + newvalues(true, false) + end + + newparam(:after) do + desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)' + + ' This is also takes a regex.' + end + + newparam(:line) do + desc 'The line to be appended to the file or used to replace matches found by the match attribute.' + end + + newparam(:path) do + desc 'The file Puppet will ensure contains the line specified by the line parameter.' + validate do |value| + unless Puppet::Util.absolute_path?(value) + raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" + end + end + end + + newparam(:replace) do + desc 'If true, replace line that matches. If false, do not write line if a match is found' + newvalues(true, false) + defaultto true + end + + newparam(:encoding) do + desc 'For files that are not UTF-8 encoded, specify encoding such as iso-8859-1' + defaultto 'UTF-8' + end + + # Autorequire the file resource if it's being managed + autorequire(:file) do + self[:path] + end + + validate do + unless self[:line] + unless (self[:ensure].to_s == 'absent') and (self[:match_for_absence].to_s == 'true') and self[:match] + raise(Puppet::Error, "line is a required attribute") + end + end + unless self[:path] + raise(Puppet::Error, "path is a required attribute") + end + end +end diff --git a/environments/prod/modules/stdlib/manifests/init.pp b/environments/prod/modules/stdlib/manifests/init.pp new file mode 100644 index 0000000..9ea22a7 --- /dev/null +++ b/environments/prod/modules/stdlib/manifests/init.pp @@ -0,0 +1,18 @@ +# Class: stdlib +# +# This module manages stdlib. Most of stdlib's features are automatically +# loaded by Puppet, but this class should be declared in order to use the +# standardized run stages. +# +# Parameters: none +# +# Actions: +# +# Declares all other classes in the stdlib module. Currently, this consists +# of stdlib::stages. +# +# Requires: nothing +# +class stdlib { + include ::stdlib::stages +} diff --git a/environments/prod/modules/stdlib/manifests/stages.pp b/environments/prod/modules/stdlib/manifests/stages.pp new file mode 100644 index 0000000..7de254c --- /dev/null +++ b/environments/prod/modules/stdlib/manifests/stages.pp @@ -0,0 +1,43 @@ +# Class: stdlib::stages +# +# This class manages a standard set of run stages for Puppet. It is managed by +# the stdlib class, and should not be declared independently. +# +# The high level stages are (in order): +# +# * setup +# * main +# * runtime +# * setup_infra +# * deploy_infra +# * setup_app +# * deploy_app +# * deploy +# +# Parameters: none +# +# Actions: +# +# Declares various run-stages for deploying infrastructure, +# language runtimes, and application layers. +# +# Requires: nothing +# +# Sample Usage: +# +# node default { +# include ::stdlib +# class { java: stage => 'runtime' } +# } +# +class stdlib::stages { + + stage { 'setup': before => Stage['main'] } + stage { 'runtime': require => Stage['main'] } + -> stage { 'setup_infra': } + -> stage { 'deploy_infra': } + -> stage { 'setup_app': } + -> stage { 'deploy_app': } + -> stage { 'deploy': } + +} diff --git a/environments/prod/modules/stdlib/metadata.json b/environments/prod/modules/stdlib/metadata.json new file mode 100644 index 0000000..419a8af --- /dev/null +++ b/environments/prod/modules/stdlib/metadata.json @@ -0,0 +1,112 @@ +{ + "name": "puppetlabs-stdlib", + "version": "4.16.0", + "author": "puppetlabs", + "summary": "Standard library of resources for Puppet modules.", + "license": "Apache-2.0", + "source": "https://github.com/puppetlabs/puppetlabs-stdlib", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "dependencies": [ + + ], + "data_provider": null, + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "10 SP4", + "11 SP1", + "12" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7", + "8" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04", + "16.04" + ] + }, + { + "operatingsystem": "Solaris", + "operatingsystemrelease": [ + "10", + "11" + ] + }, + { + "operatingsystem": "Windows", + "operatingsystemrelease": [ + "Server 2003", + "Server 2003 R2", + "Server 2008", + "Server 2008 R2", + "Server 2012", + "Server 2012 R2", + "7", + "8" + ] + }, + { + "operatingsystem": "AIX", + "operatingsystemrelease": [ + "5.3", + "6.1", + "7.1" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">=2.7.20 <5.0.0" + } + ], + "description": "Standard Library for Puppet Modules" +} diff --git a/environments/prod/modules/stdlib/spec/acceptance/abs_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/abs_spec.rb new file mode 100755 index 0000000..5af436f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/abs_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'abs function' do + describe 'success' do + it 'should accept a string' do + pp = <<-EOS + $input = '-34.56' + $output = abs($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 34.56/) + end + end + + it 'should accept a float' do + pp = <<-EOS + $input = -34.56 + $output = abs($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 34.56/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/anchor_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/anchor_spec.rb new file mode 100755 index 0000000..24a9064 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/anchor_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper_acceptance' + +describe 'anchor type' do + describe 'success' do + it 'should effect proper chaining of resources' do + pp = <<-EOS + class anchored { + anchor { 'anchored::begin': } + ~> anchor { 'anchored::end': } + } + + class anchorrefresh { + notify { 'first': } + ~> class { 'anchored': } + ~> anchor { 'final': } + } + + include anchorrefresh + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Anchor\[final\]: Triggered 'refresh'/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/any2array_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/any2array_spec.rb new file mode 100755 index 0000000..8a13911 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/any2array_spec.rb @@ -0,0 +1,49 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'any2array function' do + describe 'success' do + it 'should create an empty array' do + pp = <<-EOS + $input = '' + $output = any2array($input) + validate_array($output) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: /) + end + end + + it 'should leave arrays modified' do + pp = <<-EOS + $input = ['test', 'array'] + $output = any2array($input) + validate_array($output) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/) + end + end + + it 'should turn a hash into an array' do + pp = <<-EOS + $input = {'test' => 'array'} + $output = any2array($input) + + validate_array($output) + # Check each element of the array is a plain string. + validate_string($output[0]) + validate_string($output[1]) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/base64_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/base64_spec.rb new file mode 100755 index 0000000..e9096a7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/base64_spec.rb @@ -0,0 +1,18 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'base64 function' do + describe 'success' do + it 'should encode then decode a string' do + pp = <<-EOS + $encodestring = base64('encode', 'thestring') + $decodestring = base64('decode', $encodestring) + notify { $decodestring: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/thestring/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/bool2num_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/bool2num_spec.rb new file mode 100755 index 0000000..c69acc6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/bool2num_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'bool2num function' do + describe 'success' do + ['false', 'f', '0', 'n', 'no'].each do |bool| + it "should convert a given boolean, #{bool}, to 0" do + pp = <<-EOS + $input = "#{bool}" + $output = bool2num($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 0/) + end + end + end + + ['true', 't', '1', 'y', 'yes'].each do |bool| + it "should convert a given boolean, #{bool}, to 1" do + pp = <<-EOS + $input = "#{bool}" + $output = bool2num($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 1/) + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/build_csv.rb b/environments/prod/modules/stdlib/spec/acceptance/build_csv.rb new file mode 100755 index 0000000..62ecbf1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/build_csv.rb @@ -0,0 +1,83 @@ +#!/usr/bin/env ruby +# vim: set sw=2 sts=2 et tw=80 : +require 'rspec' + +#XXX Super ugly hack to keep from starting beaker nodes +module Kernel + # make an alias of the original require + alias_method :original_require, :require + # rewrite require + def require name + original_require name if name != 'spec_helper_acceptance' + end +end +UNSUPPORTED_PLATFORMS = [] +def fact(*args) [] end +#XXX End hax + +# Get a list of functions for test coverage +function_list = Dir[File.join(File.dirname(__FILE__),"..","..","lib","puppet","parser","functions","*.rb")].collect do |function_rb| + File.basename(function_rb,".rb") +end + +## Configure rspec to parse tests +options = RSpec::Core::ConfigurationOptions.new(['spec/acceptance']) +configuration = RSpec::configuration +world = RSpec::world +options.parse_options +options.configure(configuration) +configuration.load_spec_files + +## Collect up tests and example groups into a hash +def get_tests(children) + children.inject({}) do |memo,c| + memo[c.description] = Hash.new + memo[c.description]["groups"] = get_tests(c.children) unless c.children.empty? + memo[c.description]["tests"] = c.examples.collect { |e| + e.description unless e.pending? + }.compact unless c.examples.empty? + memo[c.description]["pending_tests"] = c.examples.collect { |e| + e.description if e.pending? + }.compact unless c.examples.empty? + memo + end +end + +def count_test_types_in(type,group) + return 0 if group.nil? + group.inject(0) do |m,(k,v)| + m += v.length if k == type + m += count_tests_in(v) if v.is_a?(Hash) + m + end +end +def count_tests_in(group) + count_test_types_in('tests',group) +end +def count_pending_tests_in(group) + count_test_types_in('pending_tests',group) +end + +# Convert tests hash to csv format +def to_csv(function_list,tests) + function_list.collect do |function_name| + if v = tests["#{function_name} function"] + positive_tests = count_tests_in(v["groups"]["success"]) + negative_tests = count_tests_in(v["groups"]["failure"]) + pending_tests = + count_pending_tests_in(v["groups"]["failure"]) + + count_pending_tests_in(v["groups"]["failure"]) + else + positive_tests = 0 + negative_tests = 0 + pending_tests = 0 + end + sprintf("%-25s, %-9d, %-9d, %-9d", function_name,positive_tests,negative_tests,pending_tests) + end.compact +end + +tests = get_tests(world.example_groups) +csv = to_csv(function_list,tests) +percentage_tested = "#{tests.count*100/function_list.count}%" +printf("%-25s, %-9s, %-9s, %-9s\n","#{percentage_tested} have tests.","Positive","Negative","Pending") +puts csv diff --git a/environments/prod/modules/stdlib/spec/acceptance/capitalize_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/capitalize_spec.rb new file mode 100755 index 0000000..03d01a8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/capitalize_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'capitalize function' do + describe 'success' do + it 'should capitalize the first letter of a string' do + pp = <<-EOS + $input = 'this is a string' + $output = capitalize($input) + notify { $output: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: This is a string/) + end + end + + it 'should capitalize the first letter of an array of strings' do + pp = <<-EOS + $input = ['this', 'is', 'a', 'string'] + $output = capitalize($input) + notify { $output: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: This/) + expect(r.stdout).to match(/Notice: Is/) + expect(r.stdout).to match(/Notice: A/) + expect(r.stdout).to match(/Notice: String/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/ceiling_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/ceiling_spec.rb new file mode 100755 index 0000000..895e4a0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/ceiling_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'ceiling function' do + describe 'success' do + it 'ceilings floats' do + pp = <<-EOS + $a = 12.8 + $b = 13 + $o = ceiling($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'ceilings integers' do + pp = <<-EOS + $a = 7 + $b = 7 + $o = ceiling($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/chomp_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/chomp_spec.rb new file mode 100755 index 0000000..56e0876 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/chomp_spec.rb @@ -0,0 +1,21 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'chomp function' do + describe 'success' do + it 'should eat the newline' do + pp = <<-EOS + $input = "test\n" + if size($input) != 5 { + fail("Size of ${input} is not 5.") + } + $output = chomp($input) + if size($output) != 4 { + fail("Size of ${input} is not 4.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/chop_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/chop_spec.rb new file mode 100755 index 0000000..0993806 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/chop_spec.rb @@ -0,0 +1,45 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'chop function' do + describe 'success' do + it 'should eat the last character' do + pp = <<-EOS + $input = "test" + if size($input) != 4 { + fail("Size of ${input} is not 4.") + } + $output = chop($input) + if size($output) != 3 { + fail("Size of ${input} is not 3.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should eat the last two characters of \r\n' do + pp = <<-'EOS' + $input = "test\r\n" + if size($input) != 6 { + fail("Size of ${input} is not 6.") + } + $output = chop($input) + if size($output) != 4 { + fail("Size of ${input} is not 4.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should not fail on empty strings' do + pp = <<-EOS + $input = "" + $output = chop($input) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/clamp_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/clamp_spec.rb new file mode 100755 index 0000000..e8ccb96 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/clamp_spec.rb @@ -0,0 +1,40 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'clamp function' do + describe 'success' do + it 'clamps list of values' do + pp = <<-EOS + $x = 17 + $y = 225 + $z = 155 + $o = clamp($x, $y, $z) + if $o == $z { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'clamps array of values' do + pp = <<-EOS + $a = [7, 19, 66] + $b = 19 + $o = clamp($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/concat_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/concat_spec.rb new file mode 100755 index 0000000..8d184d1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/concat_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'concat function' do + describe 'success' do + it 'should concat one array to another' do + pp = <<-EOS + $output = concat(['1','2','3'],['4','5','6']) + validate_array($output) + if size($output) != 6 { + fail("${output} should have 6 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat arrays and primitives to array' do + pp = <<-EOS + $output = concat(['1','2','3'],'4','5','6',['7','8','9']) + validate_array($output) + if size($output) != 9 { + fail("${output} should have 9 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat multiple arrays to one' do + pp = <<-EOS + $output = concat(['1','2','3'],['4','5','6'],['7','8','9']) + validate_array($output) + if size($output) != 9 { + fail("${output} should have 9 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat hash arguments' do + pp = <<-EOS + $output = concat([{"a" => "b"}], {"c" => "d", "e" => "f"}) + validate_array($output) + if size($output) != 2 { + fail("${output} should have 2 elements.") + } + if $output[1] != {"c" => "d", "e" => "f"} { + fail("${output} does not have the expected hash for the second element.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/count_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/count_spec.rb new file mode 100755 index 0000000..18c039d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/count_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'count function' do + describe 'success' do + it 'should count elements in an array' do + pp = <<-EOS + $input = [1,2,3,4] + $output = count($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 4/) + end + end + + it 'should count elements in an array that match a second argument' do + pp = <<-EOS + $input = [1,1,1,2] + $output = count($input, 1) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 3/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/deep_merge_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/deep_merge_spec.rb new file mode 100755 index 0000000..8222f24 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/deep_merge_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'deep_merge function' do + describe 'success' do + it 'should deep merge two hashes' do + pp = <<-EOS + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + + if $merged_hash != { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } { + fail("Hash was incorrectly merged.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/defined_with_params_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/defined_with_params_spec.rb new file mode 100755 index 0000000..a332bd6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/defined_with_params_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'defined_with_params function' do + describe 'success' do + it 'should successfully notify' do + pp = <<-EOS + user { 'dan': + ensure => present, + } + + if defined_with_params(User[dan], {'ensure' => 'present' }) { + notify { 'User defined with ensure=>present': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: User defined with ensure=>present/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/delete_at_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/delete_at_spec.rb new file mode 100755 index 0000000..d4f852a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/delete_at_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_at function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete_at(['a','b','c','b'], 1) + if $output == ['a','c','b'] { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/delete_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/delete_spec.rb new file mode 100755 index 0000000..f85b093 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/delete_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete(['a','b','c','b'], 'b') + if $output == ['a','c'] { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb new file mode 100755 index 0000000..af45a92 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_undef_values function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + if $output == { a => 'A', b => '', d => false } { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/delete_values_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/delete_values_spec.rb new file mode 100755 index 0000000..04b6920 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/delete_values_spec.rb @@ -0,0 +1,25 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_values function' do + describe 'success' do + it 'should delete elements of the hash' do + pp = <<-EOS + $a = { 'a' => 'A', 'b' => 'B', 'B' => 'C', 'd' => 'B' } + $b = { 'a' => 'A', 'B' => 'C' } + $o = delete_values($a, 'B') + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles non-hash arguments' + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/deprecation_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/deprecation_spec.rb new file mode 100644 index 0000000..7a0b34c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/deprecation_spec.rb @@ -0,0 +1,102 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'deprecation function' do + + if fact('operatingsystem') == 'windows' + test_file = 'C:/deprecation' + else + test_file = "/tmp/deprecation" + end + + # It seems that Windows needs everything to be on one line when using puppet apply -e, otherwise the manifests would be in an easier format + add_file_manifest = "\"deprecation('key', 'message') file { '#{test_file}': ensure => present, content => 'test', }\"" + remove_file_manifest = "file { '#{test_file}': ensure => absent }" + + before :all do + apply_manifest(remove_file_manifest) + end + + context 'with --strict=error', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=error', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should return an error" do + expect(@result.exit_code).to eq(1) + end + + it "should show the error message" do + expect(@result.stderr).to match(/deprecation. key. message/) + end + + describe file("#{test_file}") do + it { is_expected.not_to be_file } + end + end + + context 'with --strict=warning', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=warning', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should not return an error" do + expect(@result.exit_code).to eq(0) + end + + it "should show the error message" do + expect(@result.stderr).to match(/Warning: message/) + end + + describe file("#{test_file}") do + it { is_expected.to be_file } + end + end + + context 'with --strict=off', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=off', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should not return an error" do + expect(@result.exit_code).to eq(0) + end + + it "should not show the error message" do + expect(@result.stderr).not_to match(/Warning: message/) + end + + describe file("#{test_file}") do + it { is_expected.to be_file } + end + end + + context 'puppet 3 test', if: get_puppet_version =~ /^3/ do + before :all do + @result = on(default, puppet('apply', '--parser=future', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + after :all do + apply_manifest(remove_file_manifest) + end + + it "should return a deprecation error" do + expect(@result.stderr).to match(/Warning: message/) + end + it "should pass without error" do + expect(@result.exit_code).to eq(0) + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/difference_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/difference_spec.rb new file mode 100755 index 0000000..68f6bbe --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/difference_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'difference function' do + describe 'success' do + it 'returns non-duplicates in the first array' do + pp = <<-EOS + $a = ['a','b','c'] + $b = ['b','c','d'] + $c = ['a'] + $o = difference($a, $b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles non-array arguments' + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/dirname_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/dirname_spec.rb new file mode 100755 index 0000000..db83f0f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/dirname_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'dirname function' do + describe 'success' do + context 'absolute path' do + it 'returns the dirname' do + pp = <<-EOS + $a = '/path/to/a/file.txt' + $b = '/path/to/a' + $o = dirname($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + context 'relative path' do + it 'returns the dirname' do + pp = <<-EOS + $a = 'path/to/a/file.txt' + $b = 'path/to/a' + $o = dirname($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/downcase_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/downcase_spec.rb new file mode 100755 index 0000000..300bcfa --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/downcase_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'downcase function' do + describe 'success' do + it 'returns the downcase' do + pp = <<-EOS + $a = 'AOEU' + $b = 'aoeu' + $o = downcase($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'doesn\'t affect lowercase words' do + pp = <<-EOS + $a = 'aoeu aoeu' + $b = 'aoeu aoeu' + $o = downcase($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/empty_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/empty_spec.rb new file mode 100755 index 0000000..97b7333 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/empty_spec.rb @@ -0,0 +1,53 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'empty function' do + describe 'success' do + it 'recognizes empty strings' do + pp = <<-EOS + $a = '' + $b = true + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'recognizes non-empty strings' do + pp = <<-EOS + $a = 'aoeu' + $b = false + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles numerical values' do + pp = <<-EOS + $a = 7 + $b = false + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/ensure_resource_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/ensure_resource_spec.rb new file mode 100755 index 0000000..93f25dd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/ensure_resource_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'ensure_resource function' do + describe 'success' do + it 'ensures a resource already declared' do + apply_manifest('') + pp = <<-EOS + notify { "test": loglevel => 'err' } + ensure_resource('notify', 'test', { 'loglevel' => 'err' }) + EOS + + apply_manifest(pp, :expect_changes => true) + end + + it 'ensures a undeclared resource' do + apply_manifest('') + pp = <<-EOS + ensure_resource('notify', 'test', { 'loglevel' => 'err' }) + EOS + + apply_manifest(pp, :expect_changes => true) + end + it 'takes defaults arguments' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/flatten_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/flatten_spec.rb new file mode 100755 index 0000000..289eec9 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/flatten_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'flatten function' do + describe 'success' do + it 'flattens arrays' do + pp = <<-EOS + $a = ["a","b",["c",["d","e"],"f","g"]] + $b = ["a","b","c","d","e","f","g"] + $o = flatten($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'does not affect flat arrays' do + pp = <<-EOS + $a = ["a","b","c","d","e","f","g"] + $b = ["a","b","c","d","e","f","g"] + $o = flatten($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/floor_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/floor_spec.rb new file mode 100755 index 0000000..8259d2a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/floor_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'floor function' do + describe 'success' do + it 'floors floats' do + pp = <<-EOS + $a = 12.8 + $b = 12 + $o = floor($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'floors integers' do + pp = <<-EOS + $a = 7 + $b = 7 + $o = floor($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb new file mode 100644 index 0000000..af1b2a9 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb @@ -0,0 +1,66 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'fqdn_rand_string function' do + describe 'success' do + include_context "with faked facts" + context "when the FQDN is 'fakehost.localdomain'" do + before :each do + fake_fact("fqdn", "fakehost.localdomain") + end + + it 'generates random alphanumeric strings' do + pp = <<-eos + $l = 10 + $o = fqdn_rand_string($l) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(7oDp0KOr1b|9Acvnhkt4J)"/) + end + end + it 'generates random alphanumeric strings with custom charsets' do + pp = <<-eos + $l = 10 + $c = '0123456789' + $o = fqdn_rand_string($l, $c) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(7203048515|2383756694)"/) + end + end + it 'generates random alphanumeric strings with custom seeds' do + pp = <<-eos + $l = 10 + $s = 'seed' + $o = fqdn_rand_string($l, undef, $s) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(3HS4mbuI3E|1jJtAMs94d)"/) + end + end + it 'generates random alphanumeric strings with custom charsets and seeds' do + pp = <<-eos + $l = 10 + $c = '0123456789' + $s = 'seed' + $o = fqdn_rand_string($l, $c, $s) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(3104058232|7100592312)"/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers for length argument' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb new file mode 100755 index 0000000..66e94a9 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb @@ -0,0 +1,64 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'fqdn_rotate function' do + describe 'success' do + include_context "with faked facts" + context "when the FQDN is 'fakehost.localdomain'" do + before :each do + fake_fact("fqdn", "fakehost.localdomain") + end + + it 'rotates arrays' do + pp = <<-EOS + $a = ['a','b','c','d'] + $o = fqdn_rotate($a) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is \["d", "a", "b", "c"\]/) + end + end + it 'rotates arrays with custom seeds' do + pp = <<-EOS + $a = ['a','b','c','d'] + $s = 'seed' + $o = fqdn_rotate($a, $s) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is \["c", "d", "a", "b"\]/) + end + end + it 'rotates strings' do + pp = <<-EOS + $a = 'abcd' + $o = fqdn_rotate($a) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is "dabc"/) + end + end + it 'rotates strings with custom seeds' do + pp = <<-EOS + $a = 'abcd' + $s = 'seed' + $o = fqdn_rotate($a, $s) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is "cdab"/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles invalid arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/get_module_path_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/get_module_path_spec.rb new file mode 100755 index 0000000..3d10251 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/get_module_path_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'get_module_path function' do + describe 'success' do + it 'get_module_paths dne' do + pp = <<-EOS + $a = $::is_pe ? { + 'true' => '/etc/puppetlabs/puppet/modules/dne', + 'false' => '/etc/puppet/modules/dne', + } + $o = get_module_path('dne') + if $o == $a { + notify { 'output correct': } + } else { + notify { "failed; module path is '$o'": } + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/getparam_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/getparam_spec.rb new file mode 100755 index 0000000..bd12154 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/getparam_spec.rb @@ -0,0 +1,24 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'getparam function' do + describe 'success' do + it 'getparam a notify' do + pp = <<-EOS + notify { 'rspec': + message => 'custom rspec message', + } + $o = getparam(Notify['rspec'], 'message') + notice(inline_template('getparam is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/getparam is "custom rspec message"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/getvar_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/getvar_spec.rb new file mode 100755 index 0000000..605cdce --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/getvar_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'getvar function' do + describe 'success' do + it 'getvars from classes' do + pp = <<-EOS + class a::data { $foo = 'aoeu' } + include a::data + $b = 'aoeu' + $o = getvar("a::data::foo") + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/grep_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/grep_spec.rb new file mode 100755 index 0000000..7c35ee4 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/grep_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'grep function' do + describe 'success' do + it 'greps arrays' do + pp = <<-EOS + $a = ['aaabbb','bbbccc','dddeee'] + $b = 'bbb' + $c = ['aaabbb','bbbccc'] + $o = grep($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/has_interface_with_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/has_interface_with_spec.rb new file mode 100755 index 0000000..fd33af5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/has_interface_with_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_interface_with function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_interface_with existing ipaddress' do + pp = <<-EOS + $a = $::ipaddress + $o = has_interface_with('ipaddress', $a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is true/) + end + end + it 'has_interface_with absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.1' + $o = has_interface_with('ipaddress', $a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is false/) + end + end + it 'has_interface_with existing interface' do + pp = <<-EOS + if $osfamily == 'Solaris' or $osfamily == 'Darwin' { + $a = 'lo0' + }elsif $osfamily == 'windows' { + $a = $::kernelmajversion ? { + /6\.(2|3|4)/ => 'Ethernet0', + /6\.(0|1)/ => 'Local_Area_Connection', + /5\.(1|2)/ => undef, #Broken current in facter + } + }else { + $a = 'lo' + } + $o = has_interface_with($a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is true/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/has_ip_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/has_ip_address_spec.rb new file mode 100755 index 0000000..878d921 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/has_ip_address_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_ip_address function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_ip_address existing ipaddress' do + pp = <<-EOS + $a = '127.0.0.1' + $o = has_ip_address($a) + notice(inline_template('has_ip_address is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_address is true/) + end + end + it 'has_ip_address absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.1' + $o = has_ip_address($a) + notice(inline_template('has_ip_address is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_address is false/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/has_ip_network_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/has_ip_network_spec.rb new file mode 100755 index 0000000..f7a7d35 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/has_ip_network_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_ip_network function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_ip_network existing ipaddress' do + pp = <<-EOS + $a = '127.0.0.0' + $o = has_ip_network($a) + notice(inline_template('has_ip_network is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_network is true/) + end + end + it 'has_ip_network absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.0' + $o = has_ip_network($a) + notice(inline_template('has_ip_network is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_network is false/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/has_key_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/has_key_spec.rb new file mode 100755 index 0000000..661c122 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/has_key_spec.rb @@ -0,0 +1,41 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_key function' do + describe 'success' do + it 'has_keys in hashes' do + pp = <<-EOS + $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' } + $b = 'bbb' + $c = true + $o = has_key($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'has_keys not in hashes' do + pp = <<-EOS + $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' } + $b = 'ccc' + $c = false + $o = has_key($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-hashes' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/hash_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/hash_spec.rb new file mode 100755 index 0000000..85da50b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/hash_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'hash function' do + describe 'success' do + it 'hashs arrays' do + pp = <<-EOS + $a = ['aaa','bbb','bbb','ccc','ddd','eee'] + $b = { 'aaa' => 'bbb', 'bbb' => 'ccc', 'ddd' => 'eee' } + $o = hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles odd-length arrays' + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/intersection_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/intersection_spec.rb new file mode 100755 index 0000000..02d4e7d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/intersection_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'intersection function' do + describe 'success' do + it 'intersections arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = ['bbb','ccc','ddd','eee'] + $c = ['bbb','ccc'] + $o = intersection($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'intersections empty arrays' + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_a_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_a_spec.rb new file mode 100644 index 0000000..fb0019a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_a_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +if get_puppet_version =~ /^4/ + describe 'is_a function' do + it 'should match a string' do + pp = <<-EOS + if 'hello world'.is_a(String) { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + + it 'should not match a integer as string' do + pp = <<-EOS + if 5.is_a(String) { + notify { 'output wrong': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).not_to match(/Notice: output wrong/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_array_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_array_spec.rb new file mode 100755 index 0000000..1a83417 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_array_spec.rb @@ -0,0 +1,67 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_array function' do + describe 'success' do + it 'is_arrays arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = true + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays empty arrays' do + pp = <<-EOS + $a = [] + $b = true + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = false + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_bool_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_bool_spec.rb new file mode 100755 index 0000000..823cb46 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_bool_spec.rb @@ -0,0 +1,81 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_bool function' do + describe 'success' do + it 'is_bools arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools true' do + pp = <<-EOS + $a = true + $b = true + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools false' do + pp = <<-EOS + $a = false + $b = true + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools strings' do + pp = <<-EOS + $a = "true" + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_domain_name_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_domain_name_spec.rb new file mode 100755 index 0000000..884b0bc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_domain_name_spec.rb @@ -0,0 +1,83 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_domain_name function' do + describe 'success' do + it 'is_domain_names arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names true' do + pp = <<-EOS + $a = true + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names false' do + pp = <<-EOS + $a = false + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names strings with hyphens' do + pp = <<-EOS + $a = "3foo-bar.2bar-fuzz.com" + $b = true + $o = is_domain_name($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_domain_names strings beginning with hyphens' do + pp = <<-EOS + $a = "-bar.2bar-fuzz.com" + $b = false + $o = is_domain_name($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_domain_names hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_float_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_float_spec.rb new file mode 100755 index 0000000..0b38d94 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_float_spec.rb @@ -0,0 +1,86 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_float function' do + describe 'success' do + it 'is_floats arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + it 'is_floats true' do + pp = <<-EOS + $a = true + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + it 'is_floats strings' do + pp = <<-EOS + $a = "3.5" + $b = true + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats floats' do + pp = <<-EOS + $a = 3.5 + $b = true + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats integers' do + pp = <<-EOS + $a = 3 + $b = false + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_function_available_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_function_available_spec.rb new file mode 100755 index 0000000..f8191ee --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_function_available_spec.rb @@ -0,0 +1,58 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_function_available function' do + describe 'success' do + it 'is_function_availables arrays' do + pp = <<-EOS + $a = ['fail','include','require'] + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is false/) + end + end + it 'is_function_availables true' do + pp = <<-EOS + $a = true + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is false/) + end + end + it 'is_function_availables strings' do + pp = <<-EOS + $a = "fail" + $b = true + $o = is_function_available($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_function_availables function_availables' do + pp = <<-EOS + $a = "is_function_available" + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is true/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_hash_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_hash_spec.rb new file mode 100755 index 0000000..64f016c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_hash_spec.rb @@ -0,0 +1,63 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_hash function' do + describe 'success' do + it 'is_hashs arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $o = is_hash($a) + notice(inline_template('is_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_hash is false/) + end + end + it 'is_hashs empty hashs' do + pp = <<-EOS + $a = {} + $b = true + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_hashs strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_hashs hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = true + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_integer_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_integer_spec.rb new file mode 100755 index 0000000..7333687 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_integer_spec.rb @@ -0,0 +1,95 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_integer function' do + describe 'success' do + it 'is_integers arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers true' do + pp = <<-EOS + $a = true + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers strings' do + pp = <<-EOS + $a = "3" + $b = true + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers floats' do + pp = <<-EOS + $a = 3.5 + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers integers' do + pp = <<-EOS + $a = 3 + $b = true + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_ip_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_ip_address_spec.rb new file mode 100755 index 0000000..2c62c77 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_ip_address_spec.rb @@ -0,0 +1,80 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ip_address function' do + describe 'success' do + it 'is_ip_addresss ipv4' do + pp = <<-EOS + $a = '1.2.3.4' + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv6' do + pp = <<-EOS + $a = "fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74" + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv6 compressed' do + pp = <<-EOS + $a = "fe00::1" + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv4 out of range' do + pp = <<-EOS + $a = '1.2.3.400' + $b = false + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb new file mode 100755 index 0000000..abe26d8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ipv4_address function' do + describe 'success' do + it 'is_ipv4_addresss' do + pp = <<-EOS + $a = '1.2.3.4' + $b = true + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv4_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv4_addresss ipv4 out of range' do + pp = <<-EOS + $a = '1.2.3.400' + $b = false + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb new file mode 100755 index 0000000..73a3fa4 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb @@ -0,0 +1,66 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ipv6_address function' do + describe 'success' do + it 'is_ipv6_addresss' do + pp = <<-EOS + $a = "fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74" + $b = true + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss ipv6 compressed' do + pp = <<-EOS + $a = "fe00::1" + $b = true + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss ip out of range' do + pp = <<-EOS + $a = 'fe80:0000:cd12:d123:e2f8:47ff:fe09:gggg' + $b = false + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_mac_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_mac_address_spec.rb new file mode 100755 index 0000000..617bef6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_mac_address_spec.rb @@ -0,0 +1,38 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_mac_address function' do + describe 'success' do + it 'is_mac_addresss a mac' do + pp = <<-EOS + $a = '00:a0:1f:12:7f:a0' + $b = true + $o = is_mac_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_mac_addresss a mac out of range' do + pp = <<-EOS + $a = '00:a0:1f:12:7f:g0' + $b = false + $o = is_mac_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_numeric_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_numeric_spec.rb new file mode 100755 index 0000000..7e65384 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_numeric_spec.rb @@ -0,0 +1,95 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_numeric function' do + describe 'success' do + it 'is_numerics arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics true' do + pp = <<-EOS + $a = true + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics strings' do + pp = <<-EOS + $a = "3" + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics floats' do + pp = <<-EOS + $a = 3.5 + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics integers' do + pp = <<-EOS + $a = 3 + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/is_string_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/is_string_spec.rb new file mode 100755 index 0000000..bee5e01 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/is_string_spec.rb @@ -0,0 +1,113 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_string function' do + describe 'success' do + it 'is_strings arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings true' do + pp = <<-EOS + $a = true + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings strings' do + pp = <<-EOS + $a = "aoeu" + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is true/) + end + end + it 'is_strings number strings' do + pp = <<-EOS + $a = "3" + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is false/) + end + end + it 'is_strings floats' do + pp = <<-EOS + $a = 3.5 + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings integers' do + pp = <<-EOS + $a = 3 + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings undef' do + pp = <<-EOS + $a = undef + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is true/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb new file mode 100755 index 0000000..ae6947e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb @@ -0,0 +1,24 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'join_keys_to_values function' do + describe 'success' do + it 'join_keys_to_valuess hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb','ccc'=>'ddd'} + $b = ':' + $o = join_keys_to_values($a,$b) + notice(inline_template('join_keys_to_values is <%= @o.sort.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/join_keys_to_values is \["aaa:bbb", "ccc:ddd"\]/) + end + end + it 'handles non hashes' + it 'handles empty hashes' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/join_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/join_spec.rb new file mode 100755 index 0000000..75b88d8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/join_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'join function' do + describe 'success' do + it 'joins arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = ':' + $c = 'aaa:bbb:ccc' + $o = join($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles non arrays' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/keys_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/keys_spec.rb new file mode 100755 index 0000000..65bfe28 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/keys_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'keys function' do + describe 'success' do + it 'keyss hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb','ccc'=>'ddd'} + $o = keys($a) + notice(inline_template('keys is <%= @o.sort.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/keys is \["aaa", "ccc"\]/) + end + end + it 'handles non hashes' + it 'handles empty hashes' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/loadjson_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/loadjson_spec.rb new file mode 100644 index 0000000..ebd5307 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/loadjson_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +tmpdir = default.tmpdir('stdlib') + +describe 'loadjson function' do + describe 'success' do + it 'loadjsons array of values' do + shell("echo '{\"aaa\":1,\"bbb\":2,\"ccc\":3,\"ddd\":4}' > #{tmpdir}/testjson.json") + pp = <<-EOS + $o = loadjson('#{tmpdir}/testjson.json') + notice(inline_template('loadjson[aaa] is <%= @o["aaa"].inspect %>')) + notice(inline_template('loadjson[bbb] is <%= @o["bbb"].inspect %>')) + notice(inline_template('loadjson[ccc] is <%= @o["ccc"].inspect %>')) + notice(inline_template('loadjson[ddd] is <%= @o["ddd"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[aaa\] is 1/) + expect(r.stdout).to match(/loadjson\[bbb\] is 2/) + expect(r.stdout).to match(/loadjson\[ccc\] is 3/) + expect(r.stdout).to match(/loadjson\[ddd\] is 4/) + end + end + + it 'returns the default value if there is no file to load' do + pp = <<-EOS + $o = loadjson('#{tmpdir}/no-file.json', {'default' => 'value'}) + notice(inline_template('loadjson[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[default\] is "value"/) + end + end + + it 'returns the default value if the file was parsed with an error' do + shell("echo '!' > #{tmpdir}/testjson.json") + pp = <<-EOS + $o = loadjson('#{tmpdir}/testjson.json', {'default' => 'value'}) + notice(inline_template('loadjson[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[default\] is "value"/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/loadyaml_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/loadyaml_spec.rb new file mode 100644 index 0000000..57fb8cb --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/loadyaml_spec.rb @@ -0,0 +1,56 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +tmpdir = default.tmpdir('stdlib') + +describe 'loadyaml function' do + describe 'success' do + it 'loadyamls array of values' do + shell("echo '--- + aaa: 1 + bbb: 2 + ccc: 3 + ddd: 4' > #{tmpdir}/testyaml.yaml") + pp = <<-EOS + $o = loadyaml('#{tmpdir}/testyaml.yaml') + notice(inline_template('loadyaml[aaa] is <%= @o["aaa"].inspect %>')) + notice(inline_template('loadyaml[bbb] is <%= @o["bbb"].inspect %>')) + notice(inline_template('loadyaml[ccc] is <%= @o["ccc"].inspect %>')) + notice(inline_template('loadyaml[ddd] is <%= @o["ddd"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[aaa\] is 1/) + expect(r.stdout).to match(/loadyaml\[bbb\] is 2/) + expect(r.stdout).to match(/loadyaml\[ccc\] is 3/) + expect(r.stdout).to match(/loadyaml\[ddd\] is 4/) + end + end + + it 'returns the default value if there is no file to load' do + pp = <<-EOS + $o = loadyaml('#{tmpdir}/no-file.yaml', {'default' => 'value'}) + notice(inline_template('loadyaml[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[default\] is "value"/) + end + end + + it 'returns the default value if the file was parsed with an error' do + shell("echo '!' > #{tmpdir}/testyaml.yaml") + pp = <<-EOS + $o = loadyaml('#{tmpdir}/testyaml.yaml', {'default' => 'value'}) + notice(inline_template('loadyaml[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[default\] is "value"/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/lstrip_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/lstrip_spec.rb new file mode 100755 index 0000000..eba5d0d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/lstrip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'lstrip function' do + describe 'success' do + it 'lstrips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = lstrip($a) + notice(inline_template('lstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/lstrip is \["the ", "public ", "art", "galleries "\]/) + end + end + it 'lstrips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = lstrip($a) + notice(inline_template('lstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/lstrip is "blowzy night-frumps vex'd jack q "/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/max_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/max_spec.rb new file mode 100755 index 0000000..3caa813 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/max_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'max function' do + describe 'success' do + it 'maxs arrays' do + pp = <<-EOS + $o = max("the","public","art","galleries") + notice(inline_template('max is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/max is "the"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/member_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/member_spec.rb new file mode 100755 index 0000000..2bcadd3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/member_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'member function' do + shared_examples 'item found' do + it 'should output correctly' do + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'success' do + it 'members arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = 'ccc' + $c = true + $o = member($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + describe 'members array of integers' do + it_should_behave_like 'item found' do + let(:pp) { <<-EOS + if member( [1,2,3,4], 4 ){ + notify { 'output correct': } + } + EOS + } + end + end + describe 'members of mixed array' do + it_should_behave_like 'item found' do + let(:pp) { <<-EOS + if member( ['a','4',3], 'a' ){ + notify { 'output correct': } +} + EOS + } + end + end + it 'members arrays without members' + end + + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/merge_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/merge_spec.rb new file mode 100755 index 0000000..814db4e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/merge_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'merge function' do + describe 'success' do + it 'should merge two hashes' do + pp = <<-EOS + $a = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $b = {'two' => 'dos', 'three' => { 'five' => 5 } } + $o = merge($a, $b) + notice(inline_template('merge[one] is <%= @o["one"].inspect %>')) + notice(inline_template('merge[two] is <%= @o["two"].inspect %>')) + notice(inline_template('merge[three] is <%= @o["three"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/merge\[one\] is ("1"|1)/) + expect(r.stdout).to match(/merge\[two\] is "dos"/) + expect(r.stdout).to match(/merge\[three\] is {"five"=>("5"|5)}/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/min_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/min_spec.rb new file mode 100755 index 0000000..7b18fac --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/min_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'min function' do + describe 'success' do + it 'mins arrays' do + pp = <<-EOS + $o = min("the","public","art","galleries") + notice(inline_template('min is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/min is "art"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml new file mode 100644 index 0000000..5eebdef --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-7-x64: + roles: + - agent + - default + platform: el-7-x86_64 + hypervisor: vagrant + box: puppetlabs/centos-7.2-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml new file mode 100644 index 0000000..fef6e63 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + debian-8-x64: + roles: + - agent + - default + platform: debian-8-amd64 + hypervisor: vagrant + box: puppetlabs/debian-8.2-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/default.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..dba339c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/default.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-1404-x64: + roles: + - agent + - default + platform: ubuntu-14.04-amd64 + hypervisor: vagrant + box: puppetlabs/ubuntu-14.04-64-nocm +CONFIG: + type: foss diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml new file mode 100644 index 0000000..a3333aa --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-7-x64: + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: '["/usr/sbin/init"]' + # install various tools required to get the image up to usable levels + docker_image_commands: + - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml new file mode 100644 index 0000000..df5c319 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml @@ -0,0 +1,11 @@ +HOSTS: + debian-8-x64: + platform: debian-8-amd64 + hypervisor: docker + image: debian:8 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml new file mode 100644 index 0000000..b1efa58 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml @@ -0,0 +1,12 @@ +HOSTS: + ubuntu-1404-x64: + platform: ubuntu-14.04-amd64 + hypervisor: docker + image: ubuntu:14.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + # ensure that upstart is booting correctly in the container + - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 diff --git a/environments/prod/modules/stdlib/spec/acceptance/num2bool_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/num2bool_spec.rb new file mode 100755 index 0000000..00d0ddc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/num2bool_spec.rb @@ -0,0 +1,76 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'num2bool function' do + describe 'success' do + it 'bools positive numbers and numeric strings as true' do + pp = <<-EOS + $a = 1 + $b = "1" + $c = "50" + $ao = num2bool($a) + $bo = num2bool($b) + $co = num2bool($c) + notice(inline_template('a is <%= @ao.inspect %>')) + notice(inline_template('b is <%= @bo.inspect %>')) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/a is true/) + expect(r.stdout).to match(/b is true/) + expect(r.stdout).to match(/c is true/) + end + end + it 'bools negative numbers as false' do + pp = <<-EOS + $a = 0 + $b = -0.1 + $c = ["-50","1"] + $ao = num2bool($a) + $bo = num2bool($b) + $co = num2bool($c) + notice(inline_template('a is <%= @ao.inspect %>')) + notice(inline_template('b is <%= @bo.inspect %>')) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/a is false/) + expect(r.stdout).to match(/b is false/) + expect(r.stdout).to match(/c is false/) + end + end + end + describe 'failure' do + it 'fails on words' do + pp = <<-EOS + $a = "a" + $ao = num2bool($a) + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/) + end + + it 'fails on numberwords' do + pp = <<-EOS + $b = "1b" + $bo = num2bool($b) + notice(inline_template('b is <%= @bo.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/) + + end + + it 'fails on non-numeric/strings' do + pending "The function will call .to_s.to_i on anything not a Numeric or + String, and results in 0. Is this intended?" + pp = <<-EOS + $c = {"c" => "-50"} + $co = num2bool($c) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + expect(apply_manifest(ppc :expect_failures => true).stderr).to match(/Unable to parse/) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/parsejson_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/parsejson_spec.rb new file mode 100755 index 0000000..52133e4 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/parsejson_spec.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'parsejson function' do + describe 'success' do + it 'parses valid json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing"}' + $ao = parsejson($a) + $tests = $ao['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + + describe 'failure' do + it 'raises error on incorrect json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing",}' + $ao = parsejson($a, 'tests are using the default value') + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are using the default value/) + end + end + + it 'raises error on incorrect json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing",}' + $ao = parsejson($a) + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/expected next name/) + end + end + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = parsejson() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/parseyaml_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/parseyaml_spec.rb new file mode 100755 index 0000000..acbda46 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/parseyaml_spec.rb @@ -0,0 +1,58 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'parseyaml function' do + describe 'success' do + it 'parses valid yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n" + $o = parseyaml($a) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + + describe 'failure' do + it 'returns the default value on incorrect yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n:" + $o = parseyaml($a, {'tests' => 'using the default value'}) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "using the default value"/) + end + end + + it 'raises error on incorrect yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n:" + $o = parseyaml($a) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/(syntax error|did not find expected key)/) + end + end + + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = parseyaml() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/pick_default_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/pick_default_spec.rb new file mode 100755 index 0000000..e7e25ab --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/pick_default_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'pick_default function' do + describe 'success' do + it 'pick_defaults a default value' do + pp = <<-EOS + $a = undef + $o = pick_default($a, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "default"/) + end + end + it 'pick_defaults with no value' do + pp = <<-EOS + $a = undef + $b = undef + $o = pick_default($a,$b) + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is ""/) + end + end + it 'pick_defaults the first set value' do + pp = <<-EOS + $a = "something" + $b = "long" + $o = pick_default($a, $b, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "something"/) + end + end + end + describe 'failure' do + it 'raises error with no values' do + pp = <<-EOS + $o = pick_default() + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Must receive at least one argument/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/pick_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/pick_spec.rb new file mode 100755 index 0000000..c70b2d9 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/pick_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'pick function' do + describe 'success' do + it 'picks a default value' do + pp = <<-EOS + $a = undef + $o = pick($a, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "default"/) + end + end + it 'picks the first set value' do + pp = <<-EOS + $a = "something" + $b = "long" + $o = pick($a, $b, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "something"/) + end + end + end + describe 'failure' do + it 'raises error with all undef values' do + pp = <<-EOS + $a = undef + $b = undef + $o = pick($a, $b) + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/must receive at least one non empty value/) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/prefix_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/prefix_spec.rb new file mode 100755 index 0000000..58c691d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/prefix_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'prefix function' do + describe 'success' do + it 'prefixes array of values' do + pp = <<-EOS + $o = prefix(['a','b','c'],'p') + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \["pa", "pb", "pc"\]/) + end + end + it 'prefixs with empty array' do + pp = <<-EOS + $o = prefix([],'p') + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \[\]/) + end + end + it 'prefixs array of values with undef' do + pp = <<-EOS + $o = prefix(['a','b','c'], undef) + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \["a", "b", "c"\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/pw_hash_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/pw_hash_spec.rb new file mode 100644 index 0000000..829d087 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/pw_hash_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +# Windows and OS X do not have useful implementations of crypt(3) +describe 'pw_hash function', :unless => (['windows', 'Darwin', 'SLES']).include?(fact('operatingsystem')) do + describe 'success' do + it 'hashes passwords' do + pp = <<-EOS + $o = pw_hash('password', 'sha-512', 'salt') + notice(inline_template('pw_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/pw_hash is "\$6\$salt\$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy\.g\."/) + end + end + + it 'returns nil if no password is provided' do + pp = <<-EOS + $o = pw_hash('', 'sha-512', 'salt') + notice(inline_template('pw_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/pw_hash is nil/) + end + end + end + describe 'failure' do + it 'handles less than three arguments' + it 'handles more than three arguments' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/range_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/range_spec.rb new file mode 100755 index 0000000..f57f884 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/range_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'range function' do + describe 'success' do + it 'ranges letters' do + pp = <<-EOS + $o = range('a','d') + notice(inline_template('range is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/range is \["a", "b", "c", "d"\]/) + end + end + it 'ranges letters with a step' do + pp = <<-EOS + $o = range('a','d', '2') + notice(inline_template('range is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/range is \["a", "c"\]/) + end + end + it 'ranges letters with a negative step' + it 'ranges numbers' + it 'ranges numbers with a step' + it 'ranges numbers with a negative step' + it 'ranges numeric strings' + it 'ranges zero padded numbers' + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/reject_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/reject_spec.rb new file mode 100755 index 0000000..ce4342d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/reject_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'reject function' do + describe 'success' do + it 'rejects array of values' do + pp = <<-EOS + $o = reject(['aaa','bbb','ccc','aaaddd'], 'aaa') + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \["bbb", "ccc"\]/) + end + end + it 'rejects with empty array' do + pp = <<-EOS + $o = reject([],'aaa') + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \[\]/) + end + end + it 'rejects array of values with undef' do + pp = <<-EOS + $o = reject(['aaa','bbb','ccc','aaaddd'], undef) + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \[\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/reverse_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/reverse_spec.rb new file mode 100755 index 0000000..3b5dfad --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/reverse_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'reverse function' do + describe 'success' do + it 'reverses strings' do + pp = <<-EOS + $a = "the public art galleries" + # Anagram: Large picture halls, I bet + $o = reverse($a) + notice(inline_template('reverse is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reverse is "seirellag tra cilbup eht"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/rstrip_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/rstrip_spec.rb new file mode 100755 index 0000000..150dac1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/rstrip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'rstrip function' do + describe 'success' do + it 'rstrips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = rstrip($a) + notice(inline_template('rstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/rstrip is \[" the", " public", " art", "galleries"\]/) + end + end + it 'rstrips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = rstrip($a) + notice(inline_template('rstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/rstrip is " blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/shuffle_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/shuffle_spec.rb new file mode 100755 index 0000000..0738383 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/shuffle_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'shuffle function' do + describe 'success' do + it 'shuffles arrays' do + pp = <<-EOS + $a = ["1", "2", "3", "4", "5", "6", "7", "8", "the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = shuffle($a) + notice(inline_template('shuffle is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to_not match(/shuffle is \["1", "2", "3", "4", "5", "6", "7", "8", "the", "public", "art", "galleries"\]/) + end + end + it 'shuffles strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = shuffle($a) + notice(inline_template('shuffle is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to_not match(/shuffle is "blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/size_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/size_spec.rb new file mode 100755 index 0000000..6390c20 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/size_spec.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'size function' do + describe 'success' do + it 'single string size' do + pp = <<-EOS + $a = 'discombobulate' + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 14/) + end + end + it 'with empty string' do + pp = <<-EOS + $a = '' + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 0/) + end + end + it 'with undef' do + pp = <<-EOS + $a = undef + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 0/) + end + end + it 'strings in array' do + pp = <<-EOS + $a = ['discombobulate', 'moo'] + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 2/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/sort_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/sort_spec.rb new file mode 100755 index 0000000..e7ff7f7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/sort_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'sort function' do + describe 'success' do + it 'sorts arrays' do + pp = <<-EOS + $a = ["the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = sort($a) + notice(inline_template('sort is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/sort is \["art", "galleries", "public", "the"\]/) + end + end + it 'sorts strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = sort($a) + notice(inline_template('sort is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/sort is " '-abcdefghijklmnopqrstuvwxyz"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/squeeze_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/squeeze_spec.rb new file mode 100755 index 0000000..3324691 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/squeeze_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'squeeze function' do + describe 'success' do + it 'squeezes arrays' do + pp = <<-EOS + # Real words! + $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"] + $o = squeeze($a) + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is \["wales", "laparohysterosalpingophorectomy", "br", "godeship"\]/) + end + end + it 'squeezez arrays with an argument' + it 'squeezes strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = squeeze($a) + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is "wales laparohysterosalpingophorectomy br godeship"/) + end + end + + it 'squeezes strings with an argument' do + pp = <<-EOS + $a = "countessship duchessship governessship hostessship" + $o = squeeze($a, 's') + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is "counteship ducheship governeship hosteship"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/str2bool_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/str2bool_spec.rb new file mode 100755 index 0000000..9a8c06c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/str2bool_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'str2bool function' do + describe 'success' do + it 'works with "y"' do + pp = <<-EOS + $o = str2bool('y') + notice(inline_template('str2bool is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/str2bool is true/) + end + end + it 'works with "Y"' + it 'works with "yes"' + it 'works with "1"' + it 'works with "true"' + it 'works with "n"' + it 'works with "N"' + it 'works with "no"' + it 'works with "0"' + it 'works with "false"' + it 'works with undef' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays or strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb new file mode 100755 index 0000000..5f03924 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'str2saltedsha512 function' do + describe 'success' do + it 'works with "y"' do + pp = <<-EOS + $o = str2saltedsha512('password') + notice(inline_template('str2saltedsha512 is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/str2saltedsha512 is "[a-f0-9]{136}"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles more than one argument' + it 'handles non strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/strftime_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/strftime_spec.rb new file mode 100755 index 0000000..38521b0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/strftime_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'strftime function' do + describe 'success' do + it 'gives the Century' do + pp = <<-EOS + $o = strftime('%C') + notice(inline_template('strftime is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strftime is "20"/) + end + end + it 'takes a timezone argument' + end + describe 'failure' do + it 'handles no arguments' + it 'handles invalid format strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/strip_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/strip_spec.rb new file mode 100755 index 0000000..05cd395 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/strip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'strip function' do + describe 'success' do + it 'strips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = strip($a) + notice(inline_template('strip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strip is \["the", "public", "art", "galleries"\]/) + end + end + it 'strips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = strip($a) + notice(inline_template('strip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strip is "blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/suffix_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/suffix_spec.rb new file mode 100755 index 0000000..60a6264 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/suffix_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'suffix function' do + describe 'success' do + it 'suffixes array of values' do + pp = <<-EOS + $o = suffix(['a','b','c'],'p') + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \["ap", "bp", "cp"\]/) + end + end + it 'suffixs with empty array' do + pp = <<-EOS + $o = suffix([],'p') + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \[\]/) + end + end + it 'suffixs array of values with undef' do + pp = <<-EOS + $o = suffix(['a','b','c'], undef) + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \["a", "b", "c"\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/swapcase_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/swapcase_spec.rb new file mode 100755 index 0000000..9f94c0d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/swapcase_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'swapcase function' do + describe 'success' do + it 'works with strings' do + pp = <<-EOS + $o = swapcase('aBcD') + notice(inline_template('swapcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/swapcase is "AbCd"/) + end + end + it 'works with arrays' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays or strings' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/time_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/time_spec.rb new file mode 100755 index 0000000..dae1166 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/time_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'time function' do + describe 'success' do + it 'gives the time' do + pp = <<-EOS + $o = time() + notice(inline_template('time is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/time is (\d+)\D/) + + # When I wrote this test + expect(Integer(m[1])).to be > 1398894170 + end + end + it 'takes a timezone argument' do + pp = <<-EOS + $o = time('UTC') + notice(inline_template('time is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/time is (\d+)\D/) + + expect(Integer(m[1])).to be > 1398894170 + end + end + end + describe 'failure' do + it 'handles more arguments' + it 'handles invalid timezones' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/to_bytes_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/to_bytes_spec.rb new file mode 100755 index 0000000..b1015a3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/to_bytes_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'to_bytes function' do + describe 'success' do + it 'converts kB to B' do + pp = <<-EOS + $o = to_bytes('4 kB') + notice(inline_template('to_bytes is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/to_bytes is (\d+)\D/) + expect(m[1]).to eq("4096") + end + end + it 'works without the B in unit' + it 'works without a space before unit' + it 'works without a unit' + it 'converts fractions' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non integer arguments' + it 'handles unknown units like uB' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/try_get_value_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/try_get_value_spec.rb new file mode 100755 index 0000000..716241c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/try_get_value_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'try_get_value function' do + describe 'success' do + it 'gets a value' do + pp = <<-EOS + $data = { + 'a' => { 'b' => 'passing'} + } + + $tests = try_get_value($data, 'a/b') + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + describe 'failure' do + it 'uses a default value' do + pp = <<-EOS + $data = { + 'a' => { 'b' => 'passing'} + } + + $tests = try_get_value($data, 'c/d', 'using the default value') + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/using the default value/) + end + end + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = try_get_value() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/type_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/type_spec.rb new file mode 100755 index 0000000..5cc9470 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/type_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'type function' do + describe 'success' do + it 'types arrays' do + pp = <<-EOS + $a = ["the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = type($a) + notice(inline_template('type is <%= @o.to_s %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/type is Tuple\[String.*, String.*, String.*, String.*\]/) + end + end + it 'types strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = type($a) + notice(inline_template('type is <%= @o.to_s %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/type is String/) + end + end + it 'types hashes' + it 'types integers' + it 'types floats' + it 'types booleans' + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/union_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/union_spec.rb new file mode 100755 index 0000000..7229bf5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/union_spec.rb @@ -0,0 +1,25 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'union function' do + describe 'success' do + it 'unions arrays' do + pp = <<-EOS + $a = ["the","public"] + $b = ["art"] + $c = ["galleries"] + # Anagram: Large picture halls, I bet + $o = union($a,$b,$c) + notice(inline_template('union is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/union is \["the", "public", "art", "galleries"\]/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/unique_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/unique_spec.rb new file mode 100755 index 0000000..7fb5eca --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/unique_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'unique function' do + describe 'success' do + it 'uniques arrays' do + pp = <<-EOS + $a = ["wallless", "wallless", "brrr", "goddessship"] + $o = unique($a) + notice(inline_template('unique is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/unique is \["wallless", "brrr", "goddessship"\]/) + end + end + it 'uniques strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = unique($a) + notice(inline_template('unique is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/unique is "wales prohytingcmbd"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/upcase_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/upcase_spec.rb new file mode 100755 index 0000000..1782309 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/upcase_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'upcase function' do + describe 'success' do + it 'upcases arrays' do + pp = <<-EOS + $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"] + $o = upcase($a) + notice(inline_template('upcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/upcase is \["WALLLESS", "LAPAROHYSTEROSALPINGOOOPHORECTOMY", "BRRR", "GODDESSSHIP"\]/) + end + end + it 'upcases strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = upcase($a) + notice(inline_template('upcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/upcase is "WALLLESS LAPAROHYSTEROSALPINGOOOPHORECTOMY BRRR GODDESSSHIP"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/uriescape_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/uriescape_spec.rb new file mode 100755 index 0000000..e123425 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/uriescape_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'uriescape function' do + describe 'success' do + it 'uriescape strings' do + pp = <<-EOS + $a = ":/?#[]@!$&'()*+,;= \\\"{}" + $o = uriescape($a) + notice(inline_template('uriescape is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/uriescape is ":\/\?%23\[\]@!\$&'\(\)\*\+,;=%20%22%7B%7D"/) + end + end + it 'does nothing if a string is already safe' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb new file mode 100755 index 0000000..880850d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_absolute_path function' do + describe 'success' do + %w{ + C:/ + C:\\\\ + C:\\\\WINDOWS\\\\System32 + C:/windows/system32 + X:/foo/bar + X:\\\\foo\\\\bar + /var/tmp + /var/lib/puppet + /var/opt/../lib/puppet + }.each do |path| + it "validates a single argument #{path}" do + pp = <<-EOS + $one = '#{path}' + validate_absolute_path($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles relative paths' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_array_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_array_spec.rb new file mode 100755 index 0000000..a76321d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_array_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_array function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = ['a', 'b'] + validate_array($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = ['a', 'b'] + $two = [['c'], 'd'] + validate_array($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_array({'a' => 'hash' })}, + %{validate_array('string')}, + %{validate_array(false)}, + %{validate_array(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/is not an Array\. It looks to be a/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_augeas_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_augeas_spec.rb new file mode 100755 index 0000000..be213d3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_augeas_spec.rb @@ -0,0 +1,63 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_augeas function', :unless => (fact('osfamily') == 'windows') do + describe 'prep' do + it 'installs augeas for tests' + end + describe 'success' do + context 'valid inputs with no 3rd argument' do + { + 'root:x:0:0:root:/root:/bin/bash\n' => 'Passwd.lns', + 'proc /proc proc nodev,noexec,nosuid 0 0\n' => 'Fstab.lns' + }.each do |line,lens| + it "validates a single argument for #{lens}" do + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + validate_augeas($line, $lens) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + end + context 'valid inputs with 3rd and 4th arguments' do + it "validates a restricted value" do + line = 'root:x:0:0:root:/root:/bin/barsh\n' + lens = 'Passwd.lns' + restriction = '$file/*[shell="/bin/barsh"]' + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + $restriction = ['#{restriction}'] + validate_augeas($line, $lens, $restriction, "my custom failure message") + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/my custom failure message/) + end + end + context 'invalid inputs' do + { + 'root:x:0:0:root' => 'Passwd.lns', + '127.0.1.1' => 'Hosts.lns' + }.each do |line,lens| + it "validates a single argument for #{lens}" do + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + validate_augeas($line, $lens) + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + end + context 'garbage inputs' do + it 'raises an error on invalid inputs' + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_bool_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_bool_spec.rb new file mode 100755 index 0000000..993f9ef --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_bool_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_bool function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = true + validate_bool($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = true + $two = false + validate_bool($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_bool('true')}, + %{validate_bool('false')}, + %{validate_bool([true])}, + %{validate_bool(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/is not a boolean\. It looks to be a/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_cmd_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_cmd_spec.rb new file mode 100755 index 0000000..5fc7b94 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_cmd_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_cmd function' do + describe 'success' do + it 'validates a true command' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'echo' #shell built-in + } else { + $two = '/bin/echo' + } + validate_cmd($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a fail command' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'C:/aoeu' + } else { + $two = '/bin/aoeu' + } + validate_cmd($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates a fail command with a custom error message' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'C:/aoeu' + } else { + $two = '/bin/aoeu' + } + validate_cmd($one,$two,"aoeu is dvorak") + EOS + + apply_manifest(pp, :expect_failures => true) do |output| + expect(output.stderr).to match(/aoeu is dvorak/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper argument types' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_hash_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_hash_spec.rb new file mode 100755 index 0000000..fc0f079 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_hash_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_hash function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = { 'a' => 1 } + validate_hash($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = { 'a' => 1 } + $two = { 'b' => 2 } + validate_hash($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_hash('{ "not" => "hash" }')}, + %{validate_hash('string')}, + %{validate_hash(["array"])}, + %{validate_hash(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(//) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb new file mode 100755 index 0000000..67d3139 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_ipv4_address function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = '1.2.3.4' + validate_ipv4_address($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = '1.2.3.4' + $two = '5.6.7.8' + validate_ipv4_address($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles ipv6 addresses' + it 'handles non-ipv4 strings' + it 'handles numbers' + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb new file mode 100755 index 0000000..eaa845d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_ipv6_address function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = '3ffe:0505:0002::' + validate_ipv6_address($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = '3ffe:0505:0002::' + $two = '3ffe:0505:0001::' + validate_ipv6_address($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles ipv6 addresses' + it 'handles non-ipv6 strings' + it 'handles numbers' + it 'handles no arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_re_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_re_spec.rb new file mode 100755 index 0000000..eefb286 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_re_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_re function' do + describe 'success' do + it 'validates a string' do + pp = <<-EOS + $one = 'one' + $two = '^one$' + validate_re($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an array' do + pp = <<-EOS + $one = 'one' + $two = ['^one$', '^two'] + validate_re($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a failed array' do + pp = <<-EOS + $one = 'one' + $two = ['^two$', '^three'] + validate_re($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates a failed array with a custom error message' do + pp = <<-EOS + $one = '3.4.3' + $two = '^2.7' + validate_re($one,$two,"The $puppetversion fact does not match 2.7") + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/does not match/) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper argument types' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_slength_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_slength_spec.rb new file mode 100755 index 0000000..c29fd23 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_slength_spec.rb @@ -0,0 +1,72 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_slength function' do + describe 'success' do + it 'validates a single string max' do + pp = <<-EOS + $one = 'discombobulate' + $two = 17 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates multiple string maxes' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates min/max of strings in array' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + $three = 3 + validate_slength($one,$two,$three) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a single string max of incorrect length' do + pp = <<-EOS + $one = 'discombobulate' + $two = 1 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates multiple string maxes of incorrect length' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 3 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates multiple strings min/maxes of incorrect length' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + $three = 10 + validate_slength($one,$two,$three) + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper first argument type' + it 'handles non-strings in array of first argument' + it 'handles improper second argument type' + it 'handles improper third argument type' + it 'handles negative ranges' + it 'handles improper ranges' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/validate_string_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/validate_string_spec.rb new file mode 100755 index 0000000..f04608d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/validate_string_spec.rb @@ -0,0 +1,43 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_string function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = 'string' + validate_string($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = 'string' + $two = 'also string' + validate_string($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates undef' do + pp = <<-EOS + validate_string(undef) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a non-string' do + { + %{validate_string({ 'a' => 'hash' })} => "Hash", + %{validate_string(['array'])} => "Array", + %{validate_string(false)} => "FalseClass", + }.each do |pp,type| + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/values_at_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/values_at_spec.rb new file mode 100755 index 0000000..eb0bf4f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/values_at_spec.rb @@ -0,0 +1,73 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'values_at function' do + describe 'success' do + it 'returns a specific value' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = 1 + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b"\]/) + end + it 'returns a specific negative index value' do + pending("negative numbers don't work") + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = -1 + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["e"\]/) + end + it 'returns a range of values' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = "1-3" + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d"\]/) + end + it 'returns a negative specific value and range of values' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = ["1-3",0] + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d", "a"\]/) + end + end + describe 'failure' do + it 'handles improper number of arguments' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $output = values_at($one) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/) + end + it 'handles non-indicies arguments' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = [] + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/at least one positive index/) + end + + it 'detects index ranges smaller than the start range' + it 'handles index ranges larger than array' + it 'handles non-integer indicies' + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/values_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/values_spec.rb new file mode 100755 index 0000000..cef1c9d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/values_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'values function' do + describe 'success' do + it 'returns an array of values' do + pp = <<-EOS + $arg = { + 'a' => 1, + 'b' => 2, + 'c' => 3, + } + $output = values($arg) + notice(inline_template('<%= @output.sort.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 2, 3\]/) + end + end + describe 'failure' do + it 'handles non-hash arguments' do + pp = <<-EOS + $arg = "foo" + $output = values($arg) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires hash/) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/acceptance/zip_spec.rb b/environments/prod/modules/stdlib/spec/acceptance/zip_spec.rb new file mode 100755 index 0000000..ae22896 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/acceptance/zip_spec.rb @@ -0,0 +1,67 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'zip function' do + describe 'success' do + it 'zips two arrays of numbers together' do + pp = <<-EOS + $one = [1,2,3,4] + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\], \[3, 7\], \[4, 8\]\]/) + end + it 'zips two arrays of numbers & bools together' do + pp = <<-EOS + $one = [1,2,"three",4] + $two = [true,true,false,false] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, true\], \[2, true\], \["three", false\], \[4, false\]\]/) + end + it 'zips two arrays of numbers together and flattens them' do + # XXX This only tests the argument `true`, even though the following are valid: + # 1 t y true yes + # 0 f n false no + # undef undefined + pp = <<-EOS + $one = [1,2,3,4] + $two = [5,6,7,8] + $output = zip($one,$two,true) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 5, 2, 6, 3, 7, 4, 8\]/) + end + it 'handles unmatched length' do + # XXX Is this expected behavior? + pp = <<-EOS + $one = [1,2] + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\]\]/) + end + end + describe 'failure' do + it 'handles improper number of arguments' do + pp = <<-EOS + $one = [1,2] + $output = zip($one) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/) + end + it 'handles improper argument types' do + pp = <<-EOS + $one = "a string" + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires array/) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/absolute_path_spec.rb b/environments/prod/modules/stdlib/spec/aliases/absolute_path_spec.rb new file mode 100644 index 0000000..308a3aa --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/absolute_path_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::absolute_path', type: :class do + describe 'valid paths handling' do + %w{ + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + / + /var/tmp + /var/opt/../lib/puppet + /var/opt//lib/puppet + /var/ůťƒ8 + /var/ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Absolute_path/) } + end + end + end + + context 'relative paths' do + %w{ + relative1 + . + .. + ./foo + ../foo + etc/puppetlabs/puppet + opt/puppet/bin + relative\\windows + \var\ůťƒ8 + \var\ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Absolute_path/) } + end + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/absolutepath_spec.rb b/environments/prod/modules/stdlib/spec/aliases/absolutepath_spec.rb new file mode 100644 index 0000000..cd442f2 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/absolutepath_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::absolutepath', type: :class do + describe 'valid handling' do + %w{ + /usr2/username/bin:/usr/local/bin:/usr/bin:. + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + /var/tmp + /var/opt/../lib/puppet + /var/opt//lib/puppet + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "*/Users//nope", + "\\Users/hc/wksp/stdlib", + "C:noslashes", + "\\var\\tmp" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Variant/) } + end + end + end + + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/array_spec.rb b/environments/prod/modules/stdlib/spec/aliases/array_spec.rb new file mode 100644 index 0000000..89cc2af --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/array_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::array', type: :class do + describe 'accepts arrays' do + [ + [], + ['one'], + [1], + [{}], + [[]], + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + '', + 'one', + '1', + {}, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Array/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/bool_spec.rb b/environments/prod/modules/stdlib/spec/aliases/bool_spec.rb new file mode 100644 index 0000000..b84ab51 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/bool_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::bool', type: :class do + describe 'accepts booleans' do + [ + true, + false, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + [1], + [{}], + [true], + 'true', + 'false', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Bool/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/float_spec.rb b/environments/prod/modules/stdlib/spec/aliases/float_spec.rb new file mode 100644 index 0000000..66079c6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/float_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::float', type: :class do + describe 'accepts floats' do + [ + 3.7, + '3.7', + -3.7, + '-342.2315e-12', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x', 3, '3', -3, '-3'].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Float or Pattern(\[.*\]+)?/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/hash_spec.rb b/environments/prod/modules/stdlib/spec/aliases/hash_spec.rb new file mode 100644 index 0000000..6e5060d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/hash_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::hash', type: :class do + describe 'accepts hashes' do + [ + {}, + {'one' => "two"}, + {'wan' => 3}, + {'001' => "helly"}, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + '', + 'one', + '1', + [], + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Hash/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/httpsurl_spec.rb b/environments/prod/modules/stdlib/spec/aliases/httpsurl_spec.rb new file mode 100644 index 0000000..3e51118 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/httpsurl_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::httpsurl', type: :class do + describe 'valid handling' do + %w{ + https://hello.com + https://notcreative.org + https://notexciting.co.uk + https://graphemica.com/❤ + https://graphemica.com/緩 + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "hptts:/nah", + "https;//notrightbutclose.org", + "http://graphemica.com/❤", + "http://graphemica.com/緩" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::HTTPSUrl/) } + end + end + end + + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/httpurl_spec.rb b/environments/prod/modules/stdlib/spec/aliases/httpurl_spec.rb new file mode 100644 index 0000000..fd49a47 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/httpurl_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::httpurl', type: :class do + describe 'valid handling' do + %w{ + https://hello.com + https://notcreative.org + https://canstillaccepthttps.co.uk + http://anhttp.com + http://runningoutofideas.gov + http:// + http://graphemica.com/❤ + http://graphemica.com/緩 + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "hptts:/nah", + "https;//notrightbutclose.org", + "hts://graphemica.com/❤", + "https:graphemica.com/緩" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::HTTPUrl/) } + end + end + end + + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/integer_spec.rb b/environments/prod/modules/stdlib/spec/aliases/integer_spec.rb new file mode 100644 index 0000000..bd00c2a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/integer_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::integer', type: :class do + describe 'accepts integers' do + [ + 3, + '3', + -3, + '-3', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x', 3.7, '3.7',-3.7, '-342.2315e-12' ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Integer, Pattern(\[.*\]+)?, or Array/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/ip_address.rb b/environments/prod/modules/stdlib/spec/aliases/ip_address.rb new file mode 100644 index 0000000..67a555c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/ip_address.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ip_address', type: :class do + describe 'accepts ipv4 and ipv6 addresses' do + [ + '224.0.0.0', + '255.255.255.255', + '0.0.0.0', + '192.88.99.0', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fa76:8765:34ac:0823:ab76:eee9:0987:1111' + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + 'nope', + '77', + '4.4.4', + '2001:0db8:85a3:000000:0000:8a2e:0370:7334' + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/ipv4_spec.rb b/environments/prod/modules/stdlib/spec/aliases/ipv4_spec.rb new file mode 100644 index 0000000..6a503ad --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/ipv4_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ipv4', type: :class do + describe 'accepts ipv4 addresses' do + SharedData::IPV4_PATTERNS.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Ipv4/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/ipv6_spec.rb b/environments/prod/modules/stdlib/spec/aliases/ipv6_spec.rb new file mode 100644 index 0000000..ae90f42 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/ipv6_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ipv6', type: :class do + describe 'accepts ipv6 addresses' do + [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fa76:8765:34ac:0823:ab76:eee9:0987:1111', + 'fe80:0000:0000:0000:0204:61ff:fe9d:f156', + 'fe80:0:0:0:204:61ff:fe9d:f156', + 'fe80::204:61ff:fe9d:f156', + 'fe80:0:0:0:0204:61ff:254.157.241.86', + '::1', + 'fe80::', + '2001::', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + 'nope', + '77', + '4.4.4', + '2000:7334', + '::ffff:2.3.4', + '::ffff:257.1.2.3', + '::ffff:12345678901234567890.1.26', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Ipv6/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/numeric_spec.rb b/environments/prod/modules/stdlib/spec/aliases/numeric_spec.rb new file mode 100644 index 0000000..bc17f4f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/numeric_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::numeric', type: :class do + describe 'accepts numerics' do + [ + 3, + '3', + -3, + '-3', + 3.7, + '3.7', + -3.7, + '-342.2315e-12', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x' ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Numeric, Pattern(\[.*\]+)?, or Array/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/string_spec.rb b/environments/prod/modules/stdlib/spec/aliases/string_spec.rb new file mode 100644 index 0000000..d8fb885 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/string_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::string', type: :class do + describe 'accepts strings' do + [ + '', + 'one', + nil, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + [], + {}, + 1, + true, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a String/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/unixpath_spec.rb b/environments/prod/modules/stdlib/spec/aliases/unixpath_spec.rb new file mode 100644 index 0000000..6aecfe7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/unixpath_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::unixpath', type: :class do + describe 'valid handling' do + %w{ + /usr2/username/bin:/usr/local/bin:/usr/bin:. + /var/tmp + /Users/helencampbell/workspace/puppetlabs-stdlib + /var/ůťƒ8 + /var/ネット + /var//tmp + /var/../tmp + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "C:/whatever", + "\\var\\tmp", + "\\Users/hc/wksp/stdlib", + "*/Users//nope", + "var\ůťƒ8", + "var\ネット" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Unixpath/) } + end + end + end + + end + end +end diff --git a/environments/prod/modules/stdlib/spec/aliases/windowspath_spec.rb b/environments/prod/modules/stdlib/spec/aliases/windowspath_spec.rb new file mode 100644 index 0000000..c20e373 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/aliases/windowspath_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::windowspath', type: :class do + describe 'valid handling' do + %w{ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + X:/var/ůťƒ8 + X:/var/ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "/usr2/username/bin:/usr/local/bin:/usr/bin:.", + "C;//notright/here", + "C:noslashes", + "C:ネット", + "C:ůťƒ8" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Windowspath/) } + end + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/fixtures/dscacheutil/root b/environments/prod/modules/stdlib/spec/fixtures/dscacheutil/root new file mode 100644 index 0000000..1e34519 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/dscacheutil/root @@ -0,0 +1,8 @@ +name: root +password: * +uid: 0 +gid: 0 +dir: /var/root +shell: /bin/bash +gecos: rawr Root + diff --git a/environments/prod/modules/stdlib/spec/fixtures/lsuser/root b/environments/prod/modules/stdlib/spec/fixtures/lsuser/root new file mode 100644 index 0000000..afd59ca --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/lsuser/root @@ -0,0 +1,2 @@ +#name:home +root:/root diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp new file mode 100644 index 0000000..d77f6bd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Absolute_path type alias +class test::absolute_path( + Stdlib::Compat::Absolute_path $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp new file mode 100644 index 0000000..8321471 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Absolutepath type. Not to be confused with Stdlib::Compat::Absolute_path. +class test::absolutepath( + Stdlib::Absolutepath $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/array.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/array.pp new file mode 100644 index 0000000..84b6a5b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/array.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Array type alias +class test::array( + Stdlib::Compat::Array $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/bool.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/bool.pp new file mode 100644 index 0000000..ab5b5ea --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/bool.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Bool type alias +class test::bool( + Stdlib::Compat::Bool $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp new file mode 100644 index 0000000..bd26002 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp @@ -0,0 +1,4 @@ +# A helper class to test the ensure_resources function +class test::ensure_resources($resource_type, $title_hash, $attributes_hash) { + ensure_resources($resource_type, $title_hash, $attributes_hash) +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/float.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/float.pp new file mode 100644 index 0000000..03a603d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/float.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Float type alias +class test::float( + Stdlib::Compat::Float $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/hash.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/hash.pp new file mode 100644 index 0000000..c243570 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/hash.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Hash type alias +class test::hash( + Stdlib::Compat::Hash $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp new file mode 100644 index 0000000..9d6b92d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::HTTPSUrl type alias +class test::httpsurl( + Stdlib::HTTPSUrl $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp new file mode 100644 index 0000000..abf869e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::HTTPUrl type alias +class test::httpurl( + Stdlib::HTTPUrl $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/integer.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/integer.pp new file mode 100644 index 0000000..a4f26df --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/integer.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Integer type alias +class test::integer( + Stdlib::Compat::Integer $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp new file mode 100644 index 0000000..bbbd804 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ip_address type alias +class test::ip_address( + Stdlib::Compat::Ip_address $value, + ) { + notice("Success") + } diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp new file mode 100644 index 0000000..2e8022d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ipv4 type alias +class test::ipv4( + Stdlib::Compat::Ipv4 $value, + ) { + notice("Success") + } diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp new file mode 100644 index 0000000..7912fd6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ipv6 type alias +class test::ipv6( + Stdlib::Compat::Ipv6 $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/numeric.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/numeric.pp new file mode 100644 index 0000000..2657ebc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/numeric.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Numeric type alias +class test::numeric( + Stdlib::Compat::Numeric $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/string.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/string.pp new file mode 100644 index 0000000..6508c70 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/string.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::String type alias +class test::string( + Stdlib::Compat::String $value, + ) { + + notice("Success") + +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp new file mode 100644 index 0000000..9311109 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Unixpath type alias +class test::unixpath( + Stdlib::Unixpath $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp new file mode 100644 index 0000000..af93ed3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Windowspath type alias +class test::windowspath( + Stdlib::Windowspath $value, + ) { + notice("Success") +} diff --git a/environments/prod/modules/stdlib/spec/functions/abs_spec.rb b/environments/prod/modules/stdlib/spec/functions/abs_spec.rb new file mode 100755 index 0000000..7d2257b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/abs_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'abs' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation in puppet3', :unless => RSpec.configuration.puppet_future do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + end + + describe 'signature validation in puppet4', :if => RSpec.configuration.puppet_future do + it { pending "the puppet 4 implementation"; is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params([]).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params({}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + end + + it { is_expected.to run.with_params(-34).and_return(34) } + it { is_expected.to run.with_params("-34").and_return(34) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params("34").and_return(34) } + it { is_expected.to run.with_params(-34.5).and_return(34.5) } + it { is_expected.to run.with_params("-34.5").and_return(34.5) } + it { is_expected.to run.with_params(34.5).and_return(34.5) } + it { is_expected.to run.with_params("34.5").and_return(34.5) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/any2array_spec.rb b/environments/prod/modules/stdlib/spec/functions/any2array_spec.rb new file mode 100755 index 0000000..631657f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/any2array_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe "any2array" do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_return([]) } + it { is_expected.to run.with_params(true).and_return([true]) } + it { is_expected.to run.with_params('one').and_return(['one']) } + it { is_expected.to run.with_params('one', 'two').and_return(['one', 'two']) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two']).and_return(['one', 'two']) } + it { is_expected.to run.with_params({}).and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key', 'value']) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key', 'value']) } + + it { is_expected.to run.with_params('‰').and_return(['‰']) } + it { is_expected.to run.with_params('竹').and_return(['竹']) } + it { is_expected.to run.with_params('Ü').and_return(['Ü']) } + it { is_expected.to run.with_params('∇').and_return(['∇']) } + it { is_expected.to run.with_params('€', '万', 'Ö', '♥', '割').and_return(['€', '万', 'Ö', '♥', '割']) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/any2bool_spec.rb b/environments/prod/modules/stdlib/spec/functions/any2bool_spec.rb new file mode 100755 index 0000000..9d351ce --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/any2bool_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'any2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(true).and_return(true) } + it { is_expected.to run.with_params(false).and_return(false) } + + it { is_expected.to run.with_params('1.5').and_return(true) } + + describe 'when testing stringy values that mean "true"' do + [ 'TRUE','1', 't', 'y', 'true', 'yes'].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing stringy values that mean "false"' do + [ 'FALSE','', '0', 'f', 'n', 'false', 'no', 'undef', 'undefined', nil, :undef ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end + + describe 'when testing numeric values that mean "true"' do + [ 1,'1',1.5, '1.5'].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing numeric that mean "false"' do + [ -1, '-1', -1.5, '-1.5', '0', 0 ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end + + describe 'everything else returns true' do + [ [], {}, ['1'], [1], {:one => 1} ].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/assert_private_spec.rb b/environments/prod/modules/stdlib/spec/functions/assert_private_spec.rb new file mode 100755 index 0000000..355e0dd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/assert_private_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'assert_private' do + context 'when called from inside module' do + it "should not fail" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('foo') + + is_expected.to run.with_params() + end + end + + context "when called from private class" do + before :each do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + end + + it "should fail with a class error message" do + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('hostclass') + + is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Class foo::baz is private/) + end + + context "with an explicit failure message" do + it { is_expected.to run.with_params('failure message!').and_raise_error(Puppet::ParseError, /failure message!/) } + end + end + + context "when called from private definition" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('definition') + + is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Definition foo::baz is private/) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/base64_spec.rb b/environments/prod/modules/stdlib/spec/functions/base64_spec.rb new file mode 100755 index 0000000..842a37a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/base64_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'base64' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /first argument must be one of/) } + it { is_expected.to run.with_params("encode", ["two"]).and_raise_error(Puppet::ParseError, /second argument must be a string/) } + it { is_expected.to run.with_params("encode", 2).and_raise_error(Puppet::ParseError, /second argument must be a string/) } + it { is_expected.to run.with_params("encode", "thestring", "three").and_raise_error(Puppet::ParseError, /third argument must be one of/) } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n", "strict").and_raise_error(ArgumentError) } + + it { is_expected.to run.with_params("encode", "thestring").and_return("dGhlc3RyaW5n\n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n").and_return("thestring") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "thestring", "default").and_return("dGhlc3RyaW5n\n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n", "default").and_return("thestring") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n", "default").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "thestring", "strict").and_return("dGhlc3RyaW5n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n", "strict").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "a very long string that will cause the base64 encoder to produce output with multiple lines").and_return("YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0\nIGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5l\ncw==\n") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0\nIGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5l\ncw==\n").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + + it { is_expected.to run.with_params("encode", "a very long string that will cause the base64 encoder to produce output with multiple lines", "strict").and_return("YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==", "strict").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + + it { is_expected.to run.with_params("encode", "https://www.google.com.tw/?gws_rd=ssl#q=hello+world", "urlsafe").and_return("aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS50dy8_Z3dzX3JkPXNzbCNxPWhlbGxvK3dvcmxk") } + it { is_expected.to run.with_params("decode", "aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS50dy8_Z3dzX3JkPXNzbCNxPWhlbGxvK3dvcmxk", "urlsafe").and_return("https://www.google.com.tw/?gws_rd=ssl#q=hello+world") } + + it { is_expected.to run.with_params("encode", "https://github.com/puppetlabs/puppetlabs-stdlib/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+Add", "urlsafe").and_return("aHR0cHM6Ly9naXRodWIuY29tL3B1cHBldGxhYnMvcHVwcGV0bGFicy1zdGRsaWIvcHVsbHM_dXRmOD0lRTIlOUMlOTMmcT1pcyUzQXByK2lzJTNBb3BlbitBZGQ=") } + it { is_expected.to run.with_params("decode", "aHR0cHM6Ly9naXRodWIuY29tL3B1cHBldGxhYnMvcHVwcGV0bGFicy1zdGRsaWIvcHVsbHM_dXRmOD0lRTIlOUMlOTMmcT1pcyUzQXByK2lzJTNBb3BlbitBZGQ=", "urlsafe").and_return("https://github.com/puppetlabs/puppetlabs-stdlib/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+Add") } +end diff --git a/environments/prod/modules/stdlib/spec/functions/basename_spec.rb b/environments/prod/modules/stdlib/spec/functions/basename_spec.rb new file mode 100755 index 0000000..3e02b01 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/basename_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'basename' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext').and_return('file.ext') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext').and_return('file.ext') } + it { is_expected.to run.with_params('/path/to/a/file.ext', '.ext').and_return('file') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext', '.ext').and_return('file') } + it { is_expected.to run.with_params('scheme:///path/to/a/file.ext').and_return('file.ext') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('scheme:///√ạĺűē/竹.ext').and_return('竹.ext') } + it { is_expected.to run.with_params('ҝẽγ:/√ạĺűē/竹.ㄘ', '.ㄘ').and_return('竹') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/bool2num_spec.rb b/environments/prod/modules/stdlib/spec/functions/bool2num_spec.rb new file mode 100755 index 0000000..e506859 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/bool2num_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe 'bool2num' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + + [ true, 'true', AlsoString.new('true') ].each do |truthy| + it { is_expected.to run.with_params(truthy).and_return(1) } + end + + [ false, 'false', AlsoString.new('false') ].each do |falsey| + it { is_expected.to run.with_params(falsey).and_return(0) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/bool2str_spec.rb b/environments/prod/modules/stdlib/spec/functions/bool2str_spec.rb new file mode 100755 index 0000000..23a754b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/bool2str_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'bool2str' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + [ 'true', 'false', nil, :undef, ''].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError) } + end + it { is_expected.to run.with_params(true, 'yes', 'no', 'maybe').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true, 'maybe').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true, 0, 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true).and_return("true") } + it { is_expected.to run.with_params(false).and_return("false") } + it { is_expected.to run.with_params(true, 'yes', 'no').and_return("yes") } + it { is_expected.to run.with_params(false, 'yes', 'no').and_return("no") } + +end diff --git a/environments/prod/modules/stdlib/spec/functions/camelcase_spec.rb b/environments/prod/modules/stdlib/spec/functions/camelcase_spec.rb new file mode 100755 index 0000000..c78aa62 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/camelcase_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'camelcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("abc").and_return("Abc") } + it { is_expected.to run.with_params("aa_bb_cc").and_return("AaBbCc") } + it { is_expected.to run.with_params("_aa__bb__cc_").and_return("AaBbCc") } + it { is_expected.to run.with_params("100").and_return("100") } + it { is_expected.to run.with_params("1_00").and_return("100") } + it { is_expected.to run.with_params("_").and_return("") } + it { is_expected.to run.with_params("").and_return("") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["abc", "aa_bb_cc"]).and_return(["Abc", "AaBbCc"]) } + it { is_expected.to run.with_params(["abc", 1, "aa_bb_cc"]).and_return(["Abc", 1, "AaBbCc"]) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/capitalize_spec.rb b/environments/prod/modules/stdlib/spec/functions/capitalize_spec.rb new file mode 100755 index 0000000..7ce2e16 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/capitalize_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'capitalize' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one").and_return("One") } + it { is_expected.to run.with_params("one two").and_return("One two") } + it { is_expected.to run.with_params("ONE TWO").and_return("One two") } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("One") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["one", "two"]).and_return(["One", "Two"]) } + it { is_expected.to run.with_params(["one", 1, "two"]).and_return(["One", 1, "Two"]) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/ceiling_spec.rb b/environments/prod/modules/stdlib/spec/functions/ceiling_spec.rb new file mode 100755 index 0000000..567426f --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/ceiling_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'ceiling' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("foo").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params(-34).and_return(-34) } + it { is_expected.to run.with_params(33.1).and_return(34) } + it { is_expected.to run.with_params(-33.1).and_return(-33) } +end + diff --git a/environments/prod/modules/stdlib/spec/functions/chomp_spec.rb b/environments/prod/modules/stdlib/spec/functions/chomp_spec.rb new file mode 100755 index 0000000..56bd9b1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/chomp_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'chomp' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params("a", "b").and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("one").and_return("one") } + it { is_expected.to run.with_params("one\n").and_return("one") } + it { is_expected.to run.with_params("one\n\n").and_return("one\n") } + it { is_expected.to run.with_params(["one\n", "two", "three\n"]).and_return(["one", "two", "three"]) } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n\n")).and_return("one\n") } + it { is_expected.to run.with_params([AlsoString.new("one\n"), AlsoString.new("two"), "three\n"]).and_return(["one", "two", "three"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params("ůťƒ8\n\n").and_return("ůťƒ8\n") } + it { is_expected.to run.with_params("ネット\n\n").and_return("ネット\n") } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/chop_spec.rb b/environments/prod/modules/stdlib/spec/functions/chop_spec.rb new file mode 100755 index 0000000..b70fc37 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/chop_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'chop' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params("a", "b").and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("one").and_return("on") } + it { is_expected.to run.with_params("one\n").and_return("one") } + it { is_expected.to run.with_params("one\n\n").and_return("one\n") } + it { is_expected.to run.with_params(["one\n", "two", "three\n"]).and_return(["one", "tw", "three"]) } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("on") } + it { is_expected.to run.with_params(AlsoString.new("one\n")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n\n")).and_return("one\n") } + it { is_expected.to run.with_params([AlsoString.new("one\n"), AlsoString.new("two"), "three\n"]).and_return(["one", "tw", "three"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params("ůťƒ8\n\n").and_return("ůťƒ8\n") } + it { is_expected.to run.with_params("ネット\n\n").and_return("ネット\n") } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/clamp_spec.rb b/environments/prod/modules/stdlib/spec/functions/clamp_spec.rb new file mode 100644 index 0000000..3e2fe7a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/clamp_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'clamp' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(12, 88, 71, 190).and_raise_error(Puppet::ParseError, /Wrong number of arguments, need three to clamp/) } + it { is_expected.to run.with_params('12string', 88, 15).and_raise_error(Puppet::ParseError, /Required explicit numeric/) } + it { is_expected.to run.with_params(1, 2, {'a' => 55}).and_raise_error(Puppet::ParseError, /The Hash type is not allowed/) } + it { is_expected.to run.with_params('24', [575, 187]).and_return(187) } + it { is_expected.to run.with_params([4, 3, '99']).and_return(4) } + it { is_expected.to run.with_params(16, 750, 88).and_return(88) } + it { is_expected.to run.with_params([3, 873], 73).and_return(73) } + it { is_expected.to run.with_params([4], 8, 75).and_return(8) } + it { is_expected.to run.with_params([6], [31], 9911).and_return(31) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/concat_spec.rb b/environments/prod/modules/stdlib/spec/functions/concat_spec.rb new file mode 100755 index 0000000..6ab1b6e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/concat_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'concat' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([1]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, [2]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([1], [2], [3]).and_return([1, 2, 3]) } + it { is_expected.to run.with_params(['1','2','3'],['4','5','6']).and_return(['1','2','3','4','5','6']) } + it { is_expected.to run.with_params(['1','2','3'],'4').and_return(['1','2','3','4']) } + it { is_expected.to run.with_params(['1','2','3'],[['4','5'],'6']).and_return(['1','2','3',['4','5'],'6']) } + it { is_expected.to run.with_params(['1','2'],['3','4'],['5','6']).and_return(['1','2','3','4','5','6']) } + it { is_expected.to run.with_params(['1','2'],'3','4',['5','6']).and_return(['1','2','3','4','5','6']) } + +context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params([{"a" => "b"}], {"c" => "d", "e" => "f"}).and_return([{"a" => "b"}, {"c" => "d", "e" => "f"}]) } + it { is_expected.to run.with_params(['ấ','β','©'],['đ','ể','文字列']).and_return(['ấ','β','©','đ','ể','文字列']) } +end + + it "should leave the original array intact" do + argument1 = ['1','2','3'] + original1 = argument1.dup + argument2 = ['4','5','6'] + original2 = argument2.dup + result = subject.call([argument1,argument2]) + expect(argument1).to eq(original1) + expect(argument2).to eq(original2) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/convert_base_spec.rb b/environments/prod/modules/stdlib/spec/functions/convert_base_spec.rb new file mode 100644 index 0000000..8ab2284 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/convert_base_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'convert_base' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("asdf").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("asdf","moo","cow").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(["1"],"2").and_raise_error(Puppet::ParseError, /argument must be either a string or an integer/) } + it { is_expected.to run.with_params("1",["2"]).and_raise_error(Puppet::ParseError, /argument must be either a string or an integer/) } + it { is_expected.to run.with_params("1",1).and_raise_error(Puppet::ParseError, /base must be at least 2 and must not be greater than 36/) } + it { is_expected.to run.with_params("1",37).and_raise_error(Puppet::ParseError, /base must be at least 2 and must not be greater than 36/) } + + it "should raise a ParseError if argument 1 is a string that does not correspond to an integer in base 10" do + is_expected.to run.with_params("ten",6).and_raise_error(Puppet::ParseError, /argument must be an integer or a string corresponding to an integer in base 10/) + end + + it "should raise a ParseError if argument 2 is a string and does not correspond to an integer in base 10" do + is_expected.to run.with_params(100,"hex").and_raise_error(Puppet::ParseError, /argument must be an integer or a string corresponding to an integer in base 10/) + end + + it { is_expected.to run.with_params("11",'16').and_return('b') } + it { is_expected.to run.with_params("35",'36').and_return('z') } + it { is_expected.to run.with_params(5, 2).and_return('101') } +end diff --git a/environments/prod/modules/stdlib/spec/functions/count_spec.rb b/environments/prod/modules/stdlib/spec/functions/count_spec.rb new file mode 100755 index 0000000..3854cb8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/count_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'count' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("one").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("one", "two").and_return(1) } + it { + pending("should actually be like this, and not like above") + is_expected.to run.with_params("one", "two").and_raise_error(ArgumentError) + } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(["one", "two", "three"]).and_return(3) } + it { is_expected.to run.with_params(["one", "two", "two"], "two").and_return(2) } + it { is_expected.to run.with_params(["one", nil, "two"]).and_return(2) } + it { is_expected.to run.with_params(["one", "", "two"]).and_return(2) } + it { is_expected.to run.with_params(["one", :undef, "two"]).and_return(2) } + + it { is_expected.to run.with_params(["ổņ℮", "ŧщộ", "three"]).and_return(3) } + it { is_expected.to run.with_params(["ổņ℮", "ŧщộ", "ŧщộ"], "ŧщộ").and_return(2) } + it { is_expected.to run.with_params(["ổņ℮", nil, "ŧщộ"]).and_return(2) } + it { is_expected.to run.with_params(["ổņ℮", :undef, "ŧщộ"]).and_return(2) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/deep_merge_spec.rb b/environments/prod/modules/stdlib/spec/functions/deep_merge_spec.rb new file mode 100755 index 0000000..c91a07e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/deep_merge_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe 'deep_merge' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, '2').and_raise_error(Puppet::ParseError, /unexpected argument type String/) } + it { is_expected.to run.with_params({}, 2).and_raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) } + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}, {}).and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, '').and_return({'key' => 'value'}) } + it { is_expected.to run.with_params({'key1' => 'value1'}, {'key2' => 'value2' }).and_return({'key1' => 'value1', 'key2' => 'value2'}) } + + describe 'when arguments have key collisions' do + it 'should prefer values from the last hash' do + is_expected.to run \ + .with_params( + {'key1' => 'value1', 'key2' => 'value2' }, + {'key2' => 'replacement_value', 'key3' => 'value3'}) \ + .and_return( + {'key1' => 'value1', 'key2' => 'replacement_value', 'key3' => 'value3'}) + end + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, {'key1' => 'value2'}, {'key1' => 'value3'}) \ + .and_return({'key1' => 'value3' }) + } + end + + describe 'when arguments have subhashes' do + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, {'key2' => 'value2', 'key3' => {'subkey1' => 'value4'}}) \ + .and_return( {'key1' => 'value1', 'key2' => 'value2', 'key3' => {'subkey1' => 'value4'}}) + } + it { is_expected.to run \ + .with_params({'key1' => {'subkey1' => 'value1'}}, {'key1' => {'subkey2' => 'value2'}}) \ + .and_return( {'key1' => {'subkey1' => 'value1', 'subkey2' => 'value2'}}) + } + it { is_expected.to run \ + .with_params({'key1' => {'subkey1' => {'subsubkey1' => 'value1'}}}, {'key1' => {'subkey1' => {'subsubkey1' => 'value2'}}}) \ + .and_return( {'key1' => {'subkey1' => {'subsubkey1' => 'value2'}}}) + } + end + + it 'should not change the original hashes' do + argument1 = { 'key1' => 'value1' } + original1 = argument1.dup + argument2 = { 'key2' => 'value2' } + original2 = argument2.dup + + subject.call([argument1, argument2]) + expect(argument1).to eq(original1) + expect(argument2).to eq(original2) + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({'ĸέỹ1' => 'ϋǻļủë1'}, {'この文字列' => '万' }).and_return({'ĸέỹ1' => 'ϋǻļủë1', 'この文字列' => '万'}) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/defined_with_params_spec.rb b/environments/prod/modules/stdlib/spec/functions/defined_with_params_spec.rb new file mode 100755 index 0000000..a0db0b7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/defined_with_params_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'defined_with_params' do + describe 'when no resource is specified' do + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + end + describe 'when compared against a resource with no attributes' do + let :pre_condition do + 'user { "dan": }' + end + it { is_expected.to run.with_params('User[dan]', {}).and_return(true) } + it { is_expected.to run.with_params('User[bob]', {}).and_return(false) } + it { is_expected.to run.with_params('User[dan]', {'foo' => 'bar'}).and_return(false) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('User[ĵĭмოү]', {}).and_return(false) } + it { is_expected.to run.with_params('User[ポーラ]', {}).and_return(false) } + end + end + + describe 'when compared against a resource with attributes' do + let :pre_condition do + 'user { "dan": ensure => present, shell => "/bin/csh", managehome => false}' + end + it { is_expected.to run.with_params('User[dan]', {}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', '').and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'present'}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'present', 'managehome' => false}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'absent', 'managehome' => false}).and_return(false) } + end + + describe 'when passing undef values' do + let :pre_condition do + 'file { "/tmp/a": ensure => present }' + end + + it { is_expected.to run.with_params('File[/tmp/a]', {}).and_return(true) } + it { is_expected.to run.with_params('File[/tmp/a]', { 'ensure' => 'present', 'owner' => :undef }).and_return(true) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/delete_at_spec.rb b/environments/prod/modules/stdlib/spec/functions/delete_at_spec.rb new file mode 100755 index 0000000..0471039 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/delete_at_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'delete_at' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(['one'], 'two').and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first two.") + is_expected.to run.with_params(['one'], 0, 1).and_raise_error(Puppet::ParseError) + } + + describe 'argument validation' do + it { is_expected.to run.with_params([0, 1, 2], 3).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params([0, 1, 2], 1).and_return([0, 2]) } + it { is_expected.to run.with_params([0, 1, 2], -1).and_return([0, 1]) } + it { is_expected.to run.with_params([0, 1, 2], -4).and_return([0, 1, 2]) } + it { is_expected.to run.with_params(["ƒờở", "βāř", "ьầż"], 1).and_return(["ƒờở", "ьầż"]) } + + + it "should leave the original array intact" do + argument = [1, 2, 3] + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/delete_regex_spec.rb b/environments/prod/modules/stdlib/spec/functions/delete_regex_spec.rb new file mode 100755 index 0000000..f27a946 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/delete_regex_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'delete_regex' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two') } + it { is_expected.to run.with_params({}, 'two') } + it { is_expected.to run.with_params([], 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two', 'three', 'four').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + + describe 'deleting from an array' do + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['two', 'two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], '^t.*').and_return(['one']) } + it { is_expected.to run.with_params(['ab', 'b', 'c', 'b'], 'b').and_return(['ab', 'c']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'four').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'e').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['two', 'one', 'two', 'three', 'two'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['abracadabra'], 'abr').and_return(['abracadabra']) } + it { is_expected.to run.with_params(['abracadabra'], '^.*jimbob.*$').and_return(['abracadabra']) } + end + + describe 'deleting from an array' do + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, 'key').and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, 'key').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, 'key2') \ + .and_return( {'key1' => 'value1', 'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, ['key1', 'key2']) \ + .and_return( {'key3' => 'value3'}) + } + end + + it "should leave the original array intact" do + argument1 = ['one','two','three'] + original1 = argument1.dup + subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original hash intact" do + argument1 = {'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'} + original1 = argument1.dup + subject.call([argument1,'key2']) + expect(argument1).to eq(original1) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/delete_spec.rb b/environments/prod/modules/stdlib/spec/functions/delete_spec.rb new file mode 100755 index 0000000..4e37865 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/delete_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe 'delete' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two') } + it { is_expected.to run.with_params([], 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + + describe 'deleting from an array' do + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['two', 'two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['ab', 'b', 'c', 'b'], 'b').and_return(['ab', 'c']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'four').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'e').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['two', 'one', 'two', 'three', 'two'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'two'], ['one', 'two']).and_return(['three']) } + it { is_expected.to run.with_params(['ồאּẻ', 'ŧẅơ', 'ŧңŗё℮', 'ŧẅơ'], ['ồאּẻ', 'ŧẅơ']).and_return(['ŧңŗё℮']) } + end + + describe 'deleting from a string' do + it { is_expected.to run.with_params('', '').and_return('') } + it { is_expected.to run.with_params('bar', '').and_return('bar') } + it { is_expected.to run.with_params('', 'bar').and_return('') } + it { is_expected.to run.with_params('bar', 'bar').and_return('') } + it { is_expected.to run.with_params('barbar', 'bar').and_return('') } + it { is_expected.to run.with_params('barfoobar', 'bar').and_return('foo') } + it { is_expected.to run.with_params('foobarbabarz', 'bar').and_return('foobaz') } + it { is_expected.to run.with_params('foobarbabarz', ['foo', 'bar']).and_return('baz') } + it { is_expected.to run.with_params('ƒōōβậяβậβậяź', ['ƒōō', 'βậя']).and_return('βậź') } + + it { is_expected.to run.with_params('barfoobar', ['barbar', 'foo']).and_return('barbar') } + it { is_expected.to run.with_params('barfoobar', ['foo', 'barbar']).and_return('') } + end + + describe 'deleting from an array' do + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, 'key').and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, 'key').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, 'key2') \ + .and_return( {'key1' => 'value1', 'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, ['key1', 'key2']) \ + .and_return( {'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'ĸəұ1' => 'νãŀủĕ1', 'ĸəұ2' => 'νãŀủĕ2', 'ĸəұ3' => 'νãŀủĕ3'}, ['ĸəұ1', 'ĸəұ2']) \ + .and_return( {'ĸəұ3' => 'νãŀủĕ3'}) + } + end + + it "should leave the original array intact" do + argument1 = ['one','two','three'] + original1 = argument1.dup + result = subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original string intact" do + argument1 = 'onetwothree' + original1 = argument1.dup + result = subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original hash intact" do + argument1 = {'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'} + original1 = argument1.dup + result = subject.call([argument1,'key2']) + expect(argument1).to eq(original1) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/delete_undef_values_spec.rb b/environments/prod/modules/stdlib/spec/functions/delete_undef_values_spec.rb new file mode 100755 index 0000000..c20cee2 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/delete_undef_values_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe 'delete_undef_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + + describe 'when deleting from an array' do + [ :undef, '', nil ].each do |undef_value| + describe "when undef is represented by #{undef_value.inspect}" do + before do + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == '' + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == nil + end + it { is_expected.to run.with_params([undef_value]).and_return([]) } + it { is_expected.to run.with_params(['one',undef_value,'two','three']).and_return(['one','two','three']) } + it { is_expected.to run.with_params(['ớņέ',undef_value,'ŧשּׁō','ŧħґëə']).and_return(['ớņέ','ŧשּׁō','ŧħґëə']) } + end + + it "should leave the original argument intact" do + argument = ['one',undef_value,'two'] + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end + end + + it { is_expected.to run.with_params(['undef']).and_return(['undef']) } + end + + describe 'when deleting from a hash' do + [ :undef, '', nil ].each do |undef_value| + describe "when undef is represented by #{undef_value.inspect}" do + before do + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == '' + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == nil + end + it { is_expected.to run.with_params({'key' => undef_value}).and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'undef_key' => undef_value, 'key2' => 'value2'}) \ + .and_return({'key1' => 'value1', 'key2' => 'value2'}) + } + end + + it "should leave the original argument intact" do + argument = { 'key1' => 'value1', 'key2' => undef_value } + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end + end + + it { is_expected.to run.with_params({'key' => 'undef'}).and_return({'key' => 'undef'}) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/delete_values_spec.rb b/environments/prod/modules/stdlib/spec/functions/delete_values_spec.rb new file mode 100755 index 0000000..329fa0b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/delete_values_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'delete_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + describe 'when the first argument is not a hash' do + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(TypeError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(TypeError) } + end + + describe 'when deleting from a hash' do + it { is_expected.to run.with_params({}, 'value').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, 'non-existing value') \ + .and_return({'key1' => 'value1'}) + } + it { is_expected.to run \ + .with_params({'ҝếỵ1 ' => 'νâĺūẹ1', 'ҝếỵ2' => 'value to delete'}, 'value to delete') \ + .and_return({'ҝếỵ1 ' => 'νâĺūẹ1'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'νǎŀữ℮ ťớ đêłểťė'}, 'νǎŀữ℮ ťớ đêłểťė') \ + .and_return({'key1' => 'value1'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value to delete', 'key3' => 'value to delete'}, 'value to delete') \ + .and_return({'key1' => 'value1'}) + } + end + + it "should leave the original argument intact" do + argument = { 'key1' => 'value1', 'key2' => 'value2' } + original = argument.dup + result = subject.call([argument, 'value2']) + expect(argument).to eq(original) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/deprecation_spec.rb b/environments/prod/modules/stdlib/spec/functions/deprecation_spec.rb new file mode 100644 index 0000000..bde4e89 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/deprecation_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'deprecation' do + before(:each) { + # this is to reset the strict variable to default + Puppet.settings[:strict] = :warning + } + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + + it 'should display a single warning' do + Puppet.expects(:warning).with(includes('heelo')) + is_expected.to run.with_params('key', 'heelo') + end + + it 'should display a single warning, despite multiple calls' do + Puppet.expects(:warning).with(includes('heelo')).once + is_expected.to run.with_params('key', 'heelo') + is_expected.to run.with_params('key', 'heelo') + end + + it 'should fail twice with message, with multiple calls. when strict= :error' do + Puppet.settings[:strict] = :error + Puppet.expects(:warning).with(includes('heelo')).never + is_expected.to run.with_params('key', 'heelo').and_raise_error(RuntimeError, /deprecation. key. heelo/) + is_expected.to run.with_params('key', 'heelo').and_raise_error(RuntimeError, /deprecation. key. heelo/) + end + + it 'should display nothing, despite multiple calls. strict= :off' do + Puppet.settings[:strict] = :off + Puppet.expects(:warning).with(includes('heelo')).never + is_expected.to run.with_params('key', 'heelo') + is_expected.to run.with_params('key', 'heelo') + end + + after(:all) { + # this is to reset the strict variable to default + Puppet.settings[:strict] = :warning + } + end +elsif Puppet.version.to_f < 4.0 + # Puppet version < 4 will use these tests. + describe 'deprecation' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + before(:all) do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + end + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it 'should display a single warning' do + scope.expects(:warning).with(includes('heelo')) + is_expected.to run.with_params('key', 'heelo') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/difference_spec.rb b/environments/prod/modules/stdlib/spec/functions/difference_spec.rb new file mode 100755 index 0000000..0ae3689 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/difference_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'difference' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return([]) } + it { is_expected.to run.with_params(['one'], ['one']).and_return([]) } + it { is_expected.to run.with_params(['ớņέ'], ['']).and_return(['ớņέ']) } + it { is_expected.to run.with_params(['one'], []).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['ớņέ', 'ŧשּׁō', 'ŧħґëə', 2], ['ŧשּׁō', 'ŧħґëə']).and_return(['ớņέ', 2]) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three', 'four']).and_return(['one']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return(['1', '2', '3']) end +end diff --git a/environments/prod/modules/stdlib/spec/functions/dig44_spec.rb b/environments/prod/modules/stdlib/spec/functions/dig44_spec.rb new file mode 100644 index 0000000..a7b8a3d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/dig44_spec.rb @@ -0,0 +1,131 @@ +require 'spec_helper' + +describe 'dig44' do + + let(:data) do + { + 'a' => { + 'g' => '2', + 'e' => [ + 'f0', + 'f1', + { + 'x' => { + 'y' => 'z' + } + }, + 'f3', + ] + }, + 'b' => true, + 'c' => false, + 'd' => '1', + 'e' => :undef, + 'f' => nil, + } + end + + let(:utf8_data) do + { + 'ẵ' => { + 'в' => [ + '©', + 'ĝ', + 'に', + ] + } + } + end + + context 'single values' do + it 'should exist' do + is_expected.not_to be_nil + end + + it 'should require two arguments' do + is_expected.to run.with_params().and_raise_error(ArgumentError) + end + + it 'should fail if the data is not a structure' do + is_expected.to run.with_params('test', []).and_raise_error(Puppet::Error) + end + + it 'should fail if the path is not an array' do + is_expected.to run.with_params({}, '').and_raise_error(Puppet::Error) + end + + it 'should return the value if the value is string' do + is_expected.to run.with_params(data, ['d'], 'default').and_return('1') + end + + it 'should return true if the value is true' do + is_expected.to run.with_params(data, ['b'], 'default').and_return(true) + end + + it 'should return false if the value is false' do + is_expected.to run.with_params(data, ['c'], 'default').and_return(false) + end + + it 'should return the default if the value is nil' do + is_expected.to run.with_params(data, ['f'], 'default').and_return('default') + end + + it 'should return the default if the value is :undef (same as nil)' do + is_expected.to run.with_params(data, ['e'], 'default').and_return('default') + end + + it 'should return the default if the path is not found' do + is_expected.to run.with_params(data, ['missing'], 'default').and_return('default') + end + end + + context 'structure values' do + + it 'should be able to extract a deeply nested hash value' do + is_expected.to run.with_params(data, %w(a g), 'default').and_return('2') + end + + it 'should return the default value if the path is too long' do + is_expected.to run.with_params(data, %w(a g c d), 'default').and_return('default') + end + + it 'should support an array index (number) in the path' do + is_expected.to run.with_params(data, ['a', 'e', 1], 'default').and_return('f1') + end + + it 'should support an array index (string) in the path' do + is_expected.to run.with_params(data, %w(a e 1), 'default').and_return('f1') + end + + it 'should return the default value if an array index is not a number' do + is_expected.to run.with_params(data, %w(a b c), 'default').and_return('default') + end + + it 'should return the default value if and index is out of array length' do + is_expected.to run.with_params(data, %w(a e 5), 'default').and_return('default') + end + + it 'should be able to path though both arrays and hashes' do + is_expected.to run.with_params(data, %w(a e 2 x y), 'default').and_return('z') + end + + it 'should return "nil" if value is not found and no default value is provided' do + is_expected.to run.with_params(data, %w(a 1)).and_return(nil) + end + end + + context 'Internationalization (i18N) values' do + + it 'should be able to return a unicode character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 0]).and_return('©') + end + + it 'should be able to return a utf8 character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 1]).and_return('ĝ') + end + + it 'should be able to return a double byte character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 2]).and_return('に') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/dig_spec.rb b/environments/prod/modules/stdlib/spec/functions/dig_spec.rb new file mode 100644 index 0000000..ad16fdd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/dig_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'dig' do + + it "should exist" do + expect(Puppet::Parser::Functions.function("dig")).to eq("function_dig") + end + + it "should give a deprecation warning when called" do + scope.expects(:warning).with("dig() DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version.") + scope.function_dig([{}, []]) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/dirname_spec.rb b/environments/prod/modules/stdlib/spec/functions/dirname_spec.rb new file mode 100755 index 0000000..c494915 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/dirname_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe 'dirname' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext').and_return('/path/to/a') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext').and_return('relative_path/to/a') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('scheme:///√ạĺűē/竹.ext').and_return('scheme:///√ạĺűē') } + it { is_expected.to run.with_params('ҝẽγ:/√ạĺűē/竹.ㄘ').and_return('ҝẽγ:/√ạĺűē') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/dos2unix_spec.rb b/environments/prod/modules/stdlib/spec/functions/dos2unix_spec.rb new file mode 100644 index 0000000..97abae7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/dos2unix_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'dos2unix' do + context 'Checking parameter validity' do + it { is_expected.not_to eq(nil) } + it do + is_expected.to run.with_params.and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params('one', 'two').and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + end + end + + context 'Converting from dos to unix format' do + sample_text = "Hello\r\nWorld\r\n" + desired_output = "Hello\nWorld\n" + + it 'should output unix format' do + should run.with_params(sample_text).and_return(desired_output) + end + end + + context 'Internationalization (i18N) values' do + sample_text_utf8 = "Ħ℮ļłǿ\r\nשׁөŕłđ\r\n" + desired_output_utf8 = "Ħ℮ļłǿ\nשׁөŕłđ\n" + + sample_text_doublebyte = "こんにちは\r\n世界\r\n" + desired_output_doublebyte = "こんにちは\n世界\n" + + it 'should output uft8 string' do + should run.with_params(sample_text_utf8).and_return(desired_output_utf8) + end + + it 'should output double byte string' do + should run.with_params(sample_text_doublebyte).and_return(desired_output_doublebyte) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/downcase_spec.rb b/environments/prod/modules/stdlib/spec/functions/downcase_spec.rb new file mode 100755 index 0000000..c594560 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/downcase_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'downcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("abc").and_return("abc") } + it { is_expected.to run.with_params("Abc").and_return("abc") } + it { is_expected.to run.with_params("ABC").and_return("abc") } + + it { is_expected.to run.with_params(AlsoString.new("ABC")).and_return("abc") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["ONE", "TWO"]).and_return(["one", "two"]) } + it { is_expected.to run.with_params(["One", 1, "Two"]).and_return(["one", 1, "two"]) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/empty_spec.rb b/environments/prod/modules/stdlib/spec/functions/empty_spec.rb new file mode 100755 index 0000000..a3a25d6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/empty_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'empty' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params(0).and_return(false) } + it { is_expected.to run.with_params('').and_return(true) } + it { is_expected.to run.with_params('one').and_return(false) } + + it { is_expected.to run.with_params(AlsoString.new('')).and_return(true) } + it { is_expected.to run.with_params(AlsoString.new('one')).and_return(false) } + + it { is_expected.to run.with_params([]).and_return(true) } + it { is_expected.to run.with_params(['one']).and_return(false) } + + it { is_expected.to run.with_params({}).and_return(true) } + it { is_expected.to run.with_params({'key' => 'value'}).and_return(false) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/ensure_packages_spec.rb b/environments/prod/modules/stdlib/spec/functions/ensure_packages_spec.rb new file mode 100755 index 0000000..6f94d72 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/ensure_packages_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'ensure_packages' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + } + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(["packagename", 1]).and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("packagename") } + it { is_expected.to run.with_params(["packagename1", "packagename2"]) } + + context 'given a catalog with "package { puppet: ensure => absent }"' do + let(:pre_condition) { 'package { puppet: ensure => absent }' } + + describe 'after running ensure_package("facter")' do + before { subject.call(['facter']) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('puppet').with_ensure('absent') } + it { expect(lambda { catalogue }).to contain_package('facter').with_ensure('present') } + end + + describe 'after running ensure_package("facter", { "provider" => "gem" })' do + before { subject.call(['facter', { "provider" => "gem" }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('puppet').with_ensure('absent').without_provider() } + it { expect(lambda { catalogue }).to contain_package('facter').with_ensure('present').with_provider("gem") } + end + end + + context 'given hash of packages' do + before { subject.call([{"foo" => { "provider" => "rpm" }, "bar" => { "provider" => "gem" }}, { "ensure" => "present"}]) } + before { subject.call([{"パッケージ" => { "ensure" => "absent"}}]) } + before { subject.call([{"ρǻ¢κầģẻ" => { "ensure" => "absent"}}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('foo').with({'provider' => 'rpm', 'ensure' => 'present'}) } + it { expect(lambda { catalogue }).to contain_package('bar').with({'provider' => 'gem', 'ensure' => 'present'}) } + + context 'should run with UTF8 and double byte characters' do + it { expect(lambda { catalogue }).to contain_package('パッケージ').with({'ensure' => 'absent'}) } + it { expect(lambda { catalogue }).to contain_package('ρǻ¢κầģẻ').with({'ensure' => 'absent'}) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/ensure_resource_spec.rb b/environments/prod/modules/stdlib/spec/functions/ensure_resource_spec.rb new file mode 100755 index 0000000..5366205 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/ensure_resource_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +describe 'ensure_resource' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /Must specify a type/) } + it { is_expected.to run.with_params('type').and_raise_error(ArgumentError, /Must specify a title/) } + if Puppet::Util::Package.versioncmp(Puppet.version, '4.6.0') >= 0 + it { is_expected.to run.with_params('type', 'title', {}, 'extras').and_raise_error(ArgumentError) } + else + it { is_expected.to run.with_params('type', 'title', {}, 'extras').and_raise_error(Puppet::ParseError) } + end + + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(1,2,3).and_raise_error(Puppet::ParseError) + } + + context 'given an empty catalog' do + describe 'after running ensure_resource("user", "username1", {})' do + before { subject.call(['User', 'username1', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').without_ensure } + end + + describe 'after running ensure_resource("user", "username1", { gid => undef })' do + before { subject.call(['User', 'username1', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').without_ensure } + it { expect(lambda { catalogue }).to contain_user('username1').without_gid } + end + + describe 'after running ensure_resource("user", "username1", { ensure => present, gid => undef })' do + before { subject.call(['User', 'username1', { 'ensure' => 'present', 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username1').without_gid } + end + end + + context 'given a catalog with UTF8 chars' do + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", {})' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_ensure } + end + + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", { gid => undef })' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_ensure } + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_gid } + end + + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", { ensure => present, gid => undef })' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', { 'ensure' => 'present', 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_gid } + end + end + + context 'given a catalog with "user { username1: ensure => present }"' do + let(:pre_condition) { 'user { username1: ensure => present }' } + + describe 'after running ensure_resource("user", "username1", {})' do + before { subject.call(['User', 'username1', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + end + + describe 'after running ensure_resource("user", "username2", {})' do + before { subject.call(['User', 'username2', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username2').without_ensure } + end + + describe 'after running ensure_resource("user", "username1", { gid => undef })' do + before { subject.call(['User', 'username1', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + end + + describe 'after running ensure_resource("user", ["username1", "username2"], {})' do + before { subject.call(['User', ['username1', 'username2'], {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username2').without_ensure } + end + + describe 'when providing already set params' do + let(:params) { { 'ensure' => 'present' } } + before { subject.call(['User', ['username2', 'username3'], params]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with(params) } + it { expect(lambda { catalogue }).to contain_user('username2').with(params) } + end + + context 'when trying to add params' do + it { is_expected.to run \ + .with_params('User', 'username1', { 'ensure' => 'present', 'shell' => true }) \ + .and_raise_error(Puppet::Resource::Catalog::DuplicateResourceError, /User\[username1\] is already declared/) + } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/flatten_spec.rb b/environments/prod/modules/stdlib/spec/functions/flatten_spec.rb new file mode 100755 index 0000000..b80f3c5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/flatten_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'flatten' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params([['one']]).and_return(['one']) } + it { is_expected.to run.with_params(["a","b","c","d","e","f","g"]).and_return(["a","b","c","d","e","f","g"]) } + it { is_expected.to run.with_params([["a","b",["c",["d","e"],"f","g"]]]).and_return(["a","b","c","d","e","f","g"]) } + it { is_expected.to run.with_params(["ã","β",["ĉ",["đ","ẽ","ƒ","ġ"]]]).and_return(["ã","β","ĉ","đ","ẽ","ƒ","ġ"]) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/floor_spec.rb b/environments/prod/modules/stdlib/spec/functions/floor_spec.rb new file mode 100755 index 0000000..608c602 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/floor_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe 'floor' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("foo").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params(-34).and_return(-34) } + it { is_expected.to run.with_params(33.1).and_return(33) } + it { is_expected.to run.with_params(-33.1).and_return(-34) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb b/environments/prod/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb new file mode 100644 index 0000000..861a59e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'fqdn_rand_string' do + let(:default_charset) { %r{\A[a-zA-Z0-9]{100}\z} } + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1.5).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(-10).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("-10").and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("string").and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params([]).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params({}).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(100).and_return(default_charset) } + it { is_expected.to run.with_params("100").and_return(default_charset) } + it { is_expected.to run.with_params(100, nil).and_return(default_charset) } + it { is_expected.to run.with_params(100, '').and_return(default_charset) } + it { is_expected.to run.with_params(100, 'a').and_return(/\Aa{100}\z/) } + it { is_expected.to run.with_params(100, 'ab').and_return(/\A[ab]{100}\z/) } + it { is_expected.to run.with_params(100, 'ãβ').and_return(/\A[ãβ]{100}\z/) } + + it "provides the same 'random' value on subsequent calls for the same host" do + expect(fqdn_rand_string(10)).to eql(fqdn_rand_string(10)) + end + + it "considers the same host and same extra arguments to have the same random sequence" do + first_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"]) + second_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"]) + + expect(first_random).to eql(second_random) + end + + it "allows extra arguments to control the random value on a single host" do + first_random = fqdn_rand_string(10, :extra_identifier => [1, "different", "host"]) + second_different_random = fqdn_rand_string(10, :extra_identifier => [2, "different", "host"]) + + expect(first_random).not_to eql(second_different_random) + end + + it "should return different strings for different hosts" do + val1 = fqdn_rand_string(10, :host => "first.host.com") + val2 = fqdn_rand_string(10, :host => "second.host.com") + + expect(val1).not_to eql(val2) + end + + def fqdn_rand_string(max, args = {}) + host = args[:host] || '127.0.0.1' + charset = args[:charset] + extra = args[:extra_identifier] || [] + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn", {}).returns(host) + + function_args = [max] + if args.has_key?(:charset) or !extra.empty? + function_args << charset + end + function_args += extra + scope.function_fqdn_rand_string(function_args) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/fqdn_rotate_spec.rb b/environments/prod/modules/stdlib/spec/functions/fqdn_rotate_spec.rb new file mode 100755 index 0000000..7c1038a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/fqdn_rotate_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe 'fqdn_rotate' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('ã').and_return('ã') } + + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + + it "should rotate a string and the result should be the same size" do + expect(fqdn_rotate("asdf").size).to eq(4) + end + + it "should rotate a string to give the same results for one host" do + val1 = fqdn_rotate("abcdefg", :host => 'one') + val2 = fqdn_rotate("abcdefg", :host => 'one') + expect(val1).to eq(val2) + end + + it "allows extra arguments to control the random rotation on a single host" do + val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "different", "host"]) + val2 = fqdn_rotate("abcdefg", :extra_identifier => [2, "different", "host"]) + expect(val1).not_to eq(val2) + end + + it "considers the same host and same extra arguments to have the same random rotation" do + val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"]) + val2 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"]) + expect(val1).to eq(val2) + end + + it "should rotate a string to give different values on different hosts" do + val1 = fqdn_rotate("abcdefg", :host => 'one') + val2 = fqdn_rotate("abcdefg", :host => 'two') + expect(val1).not_to eq(val2) + end + + it "should accept objects which extend String" do + result = fqdn_rotate(AlsoString.new('asdf')) + expect(result).to eq('dfas') + end + + it "should use the Puppet::Util.deterministic_rand function" do + if Puppet::Util.respond_to?(:deterministic_rand) + Puppet::Util.expects(:deterministic_rand).with(44489829212339698569024999901561968770,4) + fqdn_rotate("asdf") + else + skip 'Puppet::Util#deterministic_rand not available' + end + end + + it "should not leave the global seed in a deterministic state" do + fqdn_rotate("asdf") + rand1 = rand() + fqdn_rotate("asdf") + rand2 = rand() + expect(rand1).not_to eql(rand2) + end + + def fqdn_rotate(value, args = {}) + host = args[:host] || '127.0.0.1' + extra = args[:extra_identifier] || [] + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn").returns(host) + + function_args = [value] + extra + scope.function_fqdn_rotate(function_args) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/fqdn_uuid_spec.rb b/environments/prod/modules/stdlib/spec/functions/fqdn_uuid_spec.rb new file mode 100644 index 0000000..d0c30d6 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/fqdn_uuid_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'fqdn_uuid' do + + context "Invalid parameters" do + it { should run.with_params().and_raise_error(ArgumentError, /No arguments given$/) } + end + + context "given string" do + it { should run.with_params('puppetlabs.com').and_return('9c70320f-6815-5fc5-ab0f-debe68bf764c') } + it { should run.with_params('google.com').and_return('64ee70a4-8cc1-5d25-abf2-dea6c79a09c8') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/get_module_path_spec.rb b/environments/prod/modules/stdlib/spec/functions/get_module_path_spec.rb new file mode 100755 index 0000000..a39e413 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/get_module_path_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe 'get_module_path' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Could not find module/) } + + class StubModule + attr_reader :path + def initialize(path) + @path = path + end + end + + describe 'when locating a module' do + let(:modulepath) { "/tmp/does_not_exist" } + let(:path_of_module_foo) { StubModule.new("/tmp/does_not_exist/foo") } + + before(:each) { Puppet[:modulepath] = modulepath } + + context 'in the default environment' do + before(:each) { Puppet::Module.expects(:find).with('foo', 'rp_env').returns(path_of_module_foo) } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + + context 'when the modulepath is a list' do + before(:each) { Puppet[:modulepath] = modulepath + 'tmp/something_else' } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + end + end + + context 'in a non-default default environment' do + let(:environment) { 'test' } + before(:each) { Puppet::Module.expects(:find).with('foo', 'test').returns(path_of_module_foo) } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + + context 'when the modulepath is a list' do + before(:each) { Puppet[:modulepath] = modulepath + 'tmp/something_else' } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/getparam_spec.rb b/environments/prod/modules/stdlib/spec/functions/getparam_spec.rb new file mode 100755 index 0000000..522ed3b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/getparam_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'getparam' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /Must specify a reference/) } + it { is_expected.to run.with_params('User[one]').and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', 2).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', []).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', {}).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + + describe 'when compared against a user resource with no params' do + let(:pre_condition) { 'user { "one": }' } + + it { is_expected.to run.with_params('User[one]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[two]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[one]', 'shell').and_return('') } + end + + describe 'when compared against a user resource with params' do + let(:pre_condition) { 'user { "one": ensure => present, shell => "/bin/sh", managehome => false, }' } + + it { is_expected.to run.with_params('User[one]', 'ensure').and_return('present') } + it { is_expected.to run.with_params('User[two]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[one]', 'shell').and_return('/bin/sh') } + it { is_expected.to run.with_params('User[one]', 'managehome').and_return(false) } + end + + describe 'when compared against a user resource with UTF8 and double byte params' do + let(:pre_condition) { 'user { ["三", "ƒốưř"]: ensure => present }' } + + it { is_expected.to run.with_params('User[三]', 'ensure').and_return('present') } + it { is_expected.to run.with_params('User[ƒốưř]', 'ensure').and_return('present') } + + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/getvar_spec.rb b/environments/prod/modules/stdlib/spec/functions/getvar_spec.rb new file mode 100755 index 0000000..55789d8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/getvar_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'getvar' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params('$::foo').and_return(nil) } + + context 'given variables in namespaces' do + let(:pre_condition) { + <<-'ENDofPUPPETcode' + class site::data { $foo = 'baz' } + include site::data + ENDofPUPPETcode + } + + it { is_expected.to run.with_params('site::data::foo').and_return('baz') } + it { is_expected.to run.with_params('::site::data::foo').and_return('baz') } + it { is_expected.to run.with_params('::site::data::bar').and_return(nil) } + end + + context 'given variables in namespaces' do + let(:pre_condition) { + <<-'ENDofPUPPETcode' + class site::info { $lock = 'ŧҺîš íš ắ śţřĭŋĝ' } + class site::new { $item = '万Ü€‰' } + include site::info + include site::new + ENDofPUPPETcode + } + + it { is_expected.to run.with_params('site::info::lock').and_return('ŧҺîš íš ắ śţřĭŋĝ') } + it { is_expected.to run.with_params('::site::new::item').and_return('万Ü€‰') } + end +end + diff --git a/environments/prod/modules/stdlib/spec/functions/grep_spec.rb b/environments/prod/modules/stdlib/spec/functions/grep_spec.rb new file mode 100755 index 0000000..d2152b1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/grep_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'grep' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("grep does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { + pending("grep does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params(1, 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['two']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 't(wo|hree)').and_return(['two', 'three']) } + it { is_expected.to run.with_params(['ờאּê', 'ţשּׂỡ', 'ţһŗəè'], 'ţ(שּׂỡ|һŗəè)').and_return(['ţשּׂỡ', 'ţһŗəè']) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/has_interface_with_spec.rb b/environments/prod/modules/stdlib/spec/functions/has_interface_with_spec.rb new file mode 100755 index 0000000..7334d38 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/has_interface_with_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'has_interface_with' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + # We need to mock out the Facts so we can specify how we expect this function + # to behave on different platforms. + context "On Mac OS X Systems" do + let(:facts) { { :interfaces => 'lo0,gif0,stf0,en1,p2p0,fw0,en0,vmnet1,vmnet8,utun0' } } + it { is_expected.to run.with_params('lo0').and_return(true) } + it { is_expected.to run.with_params('lo').and_return(false) } + end + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :ipaddress => '10.0.0.1', + :ipaddress_lo => '127.0.0.1', + :ipaddress_eth0 => '10.0.0.1', + :muppet => 'kermit', + :muppet_lo => 'mspiggy', + :muppet_eth0 => 'kermit', + } + end + + it { is_expected.to run.with_params('lo').and_return(true) } + it { is_expected.to run.with_params('lo0').and_return(false) } + it { is_expected.to run.with_params('ipaddress', '127.0.0.1').and_return(true) } + it { is_expected.to run.with_params('ipaddress', '10.0.0.1').and_return(true) } + it { is_expected.to run.with_params('ipaddress', '8.8.8.8').and_return(false) } + it { is_expected.to run.with_params('muppet', 'kermit').and_return(true) } + it { is_expected.to run.with_params('muppet', 'mspiggy').and_return(true) } + it { is_expected.to run.with_params('muppet', 'bigbird').and_return(false) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/has_ip_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/has_ip_address_spec.rb new file mode 100755 index 0000000..42a5a79 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/has_ip_address_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'has_ip_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :ipaddress => '10.0.0.1', + :ipaddress_lo => '127.0.0.1', + :ipaddress_eth0 => '10.0.0.1', + } + end + + it { is_expected.to run.with_params('127.0.0.1').and_return(true) } + it { is_expected.to run.with_params('10.0.0.1').and_return(true) } + it { is_expected.to run.with_params('8.8.8.8').and_return(false) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/has_ip_network_spec.rb b/environments/prod/modules/stdlib/spec/functions/has_ip_network_spec.rb new file mode 100755 index 0000000..57cf613 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/has_ip_network_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'has_ip_network' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :network_lo => '127.0.0.0', + :network_eth0 => '10.0.0.0', + } + end + + it { is_expected.to run.with_params('127.0.0.0').and_return(true) } + it { is_expected.to run.with_params('10.0.0.0').and_return(true) } + it { is_expected.to run.with_params('8.8.8.0').and_return(false) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/has_key_spec.rb b/environments/prod/modules/stdlib/spec/functions/has_key_spec.rb new file mode 100755 index 0000000..0e0e1cc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/has_key_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'has_key' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + it { is_expected.to run.with_params(1, "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + it { is_expected.to run.with_params([], "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + + it { is_expected.to run.with_params({ 'key' => 'value' }, "key").and_return(true) } + it { is_expected.to run.with_params({}, "key").and_return(false) } + it { is_expected.to run.with_params({ 'key' => 'value'}, "not a key").and_return(false) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({ 'κéỳ ' => '٧ậļųể' }, "κéỳ ").and_return(true) } + it { is_expected.to run.with_params({ 'キー' => '٧ậļųể' }, "キー").and_return(true) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/hash_spec.rb b/environments/prod/modules/stdlib/spec/functions/hash_spec.rb new file mode 100755 index 0000000..092474b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/hash_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'hash' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(['one']).and_raise_error(Puppet::ParseError, /Unable to compute/) } + it { is_expected.to run.with_params([]).and_return({}) } + it { is_expected.to run.with_params(['key1', 'value1']).and_return({ 'key1' => 'value1' }) } + it { is_expected.to run.with_params(['κ℮ұ1', '√āĺűẻ1']).and_return({ 'κ℮ұ1' => '√āĺűẻ1' }) } + it { is_expected.to run.with_params(['key1', 'value1', 'key2', 'value2']).and_return({ 'key1' => 'value1', 'key2' => 'value2' }) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/intersection_spec.rb b/environments/prod/modules/stdlib/spec/functions/intersection_spec.rb new file mode 100755 index 0000000..ec368a5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/intersection_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'intersection' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return([]) } + it { is_expected.to run.with_params(['one'], []).and_return([]) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['ōŋể', 'ŧשợ', 'ţђŕẽё'], ['ŧשợ', 'ţђŕẽё']).and_return(['ŧשợ', 'ţђŕẽё']) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three', 'four']).and_return(['two', 'three']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return([]) end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_a_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_a_spec.rb new file mode 100644 index 0000000..2d6cb46 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_a_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +if ENV["FUTURE_PARSER"] == 'yes' + describe 'type_of' do + pending 'teach rspec-puppet to load future-only functions under 3.7.5' do + it { is_expected.not_to eq(nil) } + end + end +end + +if Puppet.version.to_f >= 4.0 + describe 'is_a' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('', '').and_raise_error(ArgumentError) } + + it 'succeeds when comparing a string and a string' do + is_expected.to run.with_params('hello world', String).and_return(true) + end + + it 'fails when comparing an integer and a string' do + is_expected.to run.with_params(5, String).and_return(false) + end + + it 'suceeds when comparing an UTF8 and double byte characters' do + is_expected.to run.with_params('このテキスト', String).and_return(true) + is_expected.to run.with_params('ŧћịś ŧêχŧ', String).and_return(true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_array_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_array_spec.rb new file mode 100755 index 0000000..e89f54b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_array_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'is_array' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([]).and_return(true) } + it { is_expected.to run.with_params(['one']).and_return(true) } + it { is_expected.to run.with_params([1]).and_return(true) } + it { is_expected.to run.with_params([{}]).and_return(true) } + it { is_expected.to run.with_params([[]]).and_return(true) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(['1.2.3.4']).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(['1.2.3.4']).and_return(true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_bool_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_bool_spec.rb new file mode 100755 index 0000000..d21345c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_bool_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'is_bool' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(true, false).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(true).and_return(true) } + it { is_expected.to run.with_params(false).and_return(true) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params([{}]).and_return(false) } + it { is_expected.to run.with_params([[]]).and_return(false) } + it { is_expected.to run.with_params([true]).and_return(false) } + it { is_expected.to run.with_params('true').and_return(false) } + it { is_expected.to run.with_params('false').and_return(false) } + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(true).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(false).and_return(true) + end + end +end + diff --git a/environments/prod/modules/stdlib/spec/functions/is_domain_name_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_domain_name_spec.rb new file mode 100755 index 0000000..c1bf0e3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_domain_name_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'is_domain_name' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('.').and_return(true) } + it { is_expected.to run.with_params('com').and_return(true) } + it { is_expected.to run.with_params('com.').and_return(true) } + it { is_expected.to run.with_params('x.com').and_return(true) } + it { is_expected.to run.with_params('x.com.').and_return(true) } + it { is_expected.to run.with_params('foo.example.com').and_return(true) } + it { is_expected.to run.with_params('foo.example.com.').and_return(true) } + it { is_expected.to run.with_params('2foo.example.com').and_return(true) } + it { is_expected.to run.with_params('2foo.example.com.').and_return(true) } + it { is_expected.to run.with_params('www.2foo.example.com').and_return(true) } + it { is_expected.to run.with_params('www.2foo.example.com.').and_return(true) } + describe 'inputs with spaces' do + it { is_expected.to run.with_params('invalid domain').and_return(false) } + end + describe 'inputs with hyphens' do + it { is_expected.to run.with_params('foo-bar.example.com').and_return(true) } + it { is_expected.to run.with_params('foo-bar.example.com.').and_return(true) } + it { is_expected.to run.with_params('www.foo-bar.example.com').and_return(true) } + it { is_expected.to run.with_params('www.foo-bar.example.com.').and_return(true) } + it { is_expected.to run.with_params('-foo.example.com').and_return(false) } + it { is_expected.to run.with_params('-foo.example.com').and_return(false) } + end + # Values obtained from Facter values will be frozen strings + # in newer versions of Facter: + it { is_expected.to run.with_params('www.example.com'.freeze).and_return(true) } + describe 'top level domain must be alphabetic if there are multiple labels' do + it { is_expected.to run.with_params('2com').and_return(true) } + it { is_expected.to run.with_params('www.example.2com').and_return(false) } + end + describe 'IP addresses are not domain names' do + it { is_expected.to run.with_params('192.168.1.1').and_return(false) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_email_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_email_address_spec.rb new file mode 100755 index 0000000..8b7b358 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_email_address_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe 'is_email_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('bob@gmail.com').and_return(true) } + it { is_expected.to run.with_params('alice+puppetlabs.com@gmail.com').and_return(true) } + it { is_expected.to run.with_params('peter.parker@gmail.com').and_return(true) } + it { is_expected.to run.with_params('1.2.3@domain').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5@').and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_float_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_float_spec.rb new file mode 100755 index 0000000..af3322e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_float_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'is_float' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0.1, 0.2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'passing a string' do + it { is_expected.to run.with_params('0.1').and_return(true) } + it { is_expected.to run.with_params('1.0').and_return(true) } + it { is_expected.to run.with_params('1').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params('one 1.0').and_return(false) } + it { is_expected.to run.with_params('1.0 one').and_return(false) } + end + + describe 'passing numbers' do + it { is_expected.to run.with_params(0.1).and_return(true) } + it { is_expected.to run.with_params(1.0).and_return(true) } + it { is_expected.to run.with_params(1).and_return(false) } + end + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(2.2).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(1.0).and_return(true) + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_function_available.rb b/environments/prod/modules/stdlib/spec/functions/is_function_available.rb new file mode 100755 index 0000000..44f08c0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_function_available.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'is_function_available' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('include').and_return(true) } + it { is_expected.to run.with_params('no_such_function').and_return(false) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_hash_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_hash_spec.rb new file mode 100755 index 0000000..c2599a0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_hash_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe 'is_hash' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params({}).and_return(true) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_integer_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_integer_spec.rb new file mode 100755 index 0000000..b296830 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_integer_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe 'is_integer' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(3).and_return(true) } + it { is_expected.to run.with_params('3').and_return(true) } + it { is_expected.to run.with_params(-3).and_return(true) } + it { is_expected.to run.with_params('-3').and_return(true) } + + it { is_expected.to run.with_params(3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + it { is_expected.to run.with_params(-3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('0001234').and_return(false) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(50).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(50).and_return(true) + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_ip_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_ip_address_spec.rb new file mode 100755 index 0000000..39525d7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_ip_address_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'is_ip_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('1.2.3.4').and_return(true) } + it { is_expected.to run.with_params('1.2.3.255').and_return(true) } + it { is_expected.to run.with_params('1.2.3.256').and_return(false) } + it { is_expected.to run.with_params('1.2.3').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5').and_return(false) } + it { is_expected.to run.with_params('fe00::1').and_return(true) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74').and_return(true) } + it { is_expected.to run.with_params('FE80:0000:CD12:D123:E2F8:47FF:FE09:DD74').and_return(true) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:zzzz').and_return(false) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09').and_return(false) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74:dd74').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1.2.3.4').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('1.2.3.4').and_return(true) + end + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_ipv4_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_ipv4_address_spec.rb new file mode 100644 index 0000000..985260c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_ipv4_address_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'is_ipv4_address' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + SharedData::IPV4_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first).and_return(true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_ipv6_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_ipv6_address_spec.rb new file mode 100644 index 0000000..acd6a87 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_ipv6_address_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'is_ipv6_address' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) } + it { is_expected.to run.with_params('85a3:0000:0000:8a2e:0370:7334:100.100.100.100').and_return(true) } + it { is_expected.to run.with_params('1.2.3').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_mac_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_mac_address_spec.rb new file mode 100755 index 0000000..c1e33b8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_mac_address_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'is_mac_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('00:a0:1f:12:7f:a0').and_return(true) } + it { is_expected.to run.with_params('00:A0:1F:12:7F:A0').and_return(true) } + it { is_expected.to run.with_params('00:00:00:00:00:0g').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + + context 'function can handle UTF8 and double byte characters' do + it { is_expected.to run.with_params('ƒốưř').and_return(false) } + it { is_expected.to run.with_params('三+').and_return(false) } + end + + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params(1).and_return(false) + } + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params({}).and_return(false) + } + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params([]).and_return(false) + } +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_numeric_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_numeric_spec.rb new file mode 100755 index 0000000..5962d8a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_numeric_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe 'is_numeric' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(3).and_return(true) } + it { is_expected.to run.with_params('3').and_return(true) } + it { is_expected.to run.with_params(-3).and_return(true) } + it { is_expected.to run.with_params('-3').and_return(true) } + + it { is_expected.to run.with_params(3.7).and_return(true) } + it { is_expected.to run.with_params('3.7').and_return(true) } + it { is_expected.to run.with_params(-3.7).and_return(true) } + it { is_expected.to run.with_params('3.7').and_return(true) } + + it { is_expected.to run.with_params('-342.2315e-12').and_return(true) } + + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('0001234').and_return(false) } + it { is_expected.to run.with_params(' - 1234').and_return(false) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(7).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(7).and_return(true) + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/is_string_spec.rb b/environments/prod/modules/stdlib/spec/functions/is_string_spec.rb new file mode 100755 index 0000000..e92f85c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/is_string_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'is_string' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + + it { is_expected.to run.with_params(3).and_return(false) } + it { is_expected.to run.with_params('3').and_return(false) } + it { is_expected.to run.with_params(-3).and_return(false) } + it { is_expected.to run.with_params('-3').and_return(false) } + + it { is_expected.to run.with_params(3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + it { is_expected.to run.with_params(-3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('one').and_return(true) } + it { is_expected.to run.with_params('0001234').and_return(true) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('sponge').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('bob').and_return(true) + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/join_keys_to_values_spec.rb b/environments/prod/modules/stdlib/spec/functions/join_keys_to_values_spec.rb new file mode 100755 index 0000000..0a2a50c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/join_keys_to_values_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'join_keys_to_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Takes exactly two arguments/) } + it { is_expected.to run.with_params({}, '', '').and_raise_error(Puppet::ParseError, /Takes exactly two arguments/) } + it { is_expected.to run.with_params('one', '').and_raise_error(TypeError, /The first argument must be a hash/) } + it { is_expected.to run.with_params({}, 2).and_raise_error(TypeError, /The second argument must be a string/) } + + it { is_expected.to run.with_params({}, '').and_return([]) } + it { is_expected.to run.with_params({}, ':').and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }, '').and_return(['keyvalue']) } + it { is_expected.to run.with_params({ 'key' => 'value' }, ':').and_return(['key:value']) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({ 'ҝẽγ' => '√ạĺűē' }, ':').and_return(['ҝẽγ:√ạĺűē']) } + it { is_expected.to run.with_params({ 'ҝẽγ' => '√ạĺűē' }, '万').and_return(['ҝẽγ万√ạĺűē']) } + end + + it { is_expected.to run.with_params({ 'key' => nil }, ':').and_return(['key:']) } + it 'should run join_keys_to_values(, ":") and return the proper array' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2' }, ':']) + expect(result.sort).to eq(['key1:value1', 'key2:value2'].sort) + end + it 'should run join_keys_to_values(, " ") and return the proper array' do + result = subject.call([{ 'key1' => 'value1', 'key2' => ['value2', 'value3'] }, ' ']) + expect(result.sort).to eq(['key1 value1', 'key2 value2', 'key2 value3'].sort) + end +end + diff --git a/environments/prod/modules/stdlib/spec/functions/join_spec.rb b/environments/prod/modules/stdlib/spec/functions/join_spec.rb new file mode 100755 index 0000000..98852d5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/join_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'join' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], '', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Requires array to work with/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /Requires string to work with/) } + + it { is_expected.to run.with_params([]).and_return('') } + it { is_expected.to run.with_params([], ':').and_return('') } + it { is_expected.to run.with_params(['one']).and_return('one') } + it { is_expected.to run.with_params(['one'], ':').and_return('one') } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return('onetwothree') } + it { is_expected.to run.with_params(['one', 'two', 'three'], ':').and_return('one:two:three') } + it { is_expected.to run.with_params(['ōŋể', 'ŧשợ', 'ţђŕẽё'], ':').and_return('ōŋể:ŧשợ:ţђŕẽё') } +end diff --git a/environments/prod/modules/stdlib/spec/functions/keys_spec.rb b/environments/prod/modules/stdlib/spec/functions/keys_spec.rb new file mode 100755 index 0000000..fc7d6d8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/keys_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'keys' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params({}).and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key']) } + it 'should return the array of keys' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2' }]) + expect(result).to match_array(['key1', 'key2']) + end + + it 'should run with UTF8 and double byte characters' do + result = subject.call([{ 'ҝểү' => '√ẳŀμệ', 'キー' => '値' }]) + expect(result).to match_array(['ҝểү', 'キー']) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/length.rb b/environments/prod/modules/stdlib/spec/functions/length.rb new file mode 100755 index 0000000..d1ab003 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/length.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'length' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /'length' expects 1 argument, got none/) } + it { is_expected.to run.with_params([], 'extra').and_raise_error(ArgumentError, /'length' expects 1 argument, got 2/) } + it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, /expects a value of type String, Array, or Hash, got Integer/) } + it { is_expected.to run.with_params(true).and_raise_error(ArgumentError, /expects a value of type String, Array, or Hash, got Boolean/) } + it { is_expected.to run.with_params('1').and_return(1) } + it { is_expected.to run.with_params('1.0').and_return(3) } + it { is_expected.to run.with_params([]).and_return(0) } + it { is_expected.to run.with_params(['a']).and_return(1) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(3) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(4) } + + it { is_expected.to run.with_params({}).and_return(0) } + it { is_expected.to run.with_params({'1' => '2'}).and_return(1) } + it { is_expected.to run.with_params({'1' => '2', '4' => '4'}).and_return(2) } + it { is_expected.to run.with_params({'€' => '@', '竹' => 'ǿňè'}).and_return(2) } + + it { is_expected.to run.with_params('').and_return(0) } + it { is_expected.to run.with_params('a').and_return(1) } + it { is_expected.to run.with_params('abc').and_return(3) } + it { is_expected.to run.with_params('abcd').and_return(4) } + it { is_expected.to run.with_params('万').and_return(1) } + it { is_expected.to run.with_params('āβćđ').and_return(4) } + + context 'when using a class extending String' do + it 'should call its size method' do + value = AlsoString.new('asdfghjkl') + value.expects(:length).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/load_module_metadata_spec.rb b/environments/prod/modules/stdlib/spec/functions/load_module_metadata_spec.rb new file mode 100755 index 0000000..9496fcb --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/load_module_metadata_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe 'load_module_metadata' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe "when calling with valid arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"name": "puppetlabs-stdlib"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"name": "puppetlabs-stdlib"}') + end + + context "when calling with valid utf8 and double byte character arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"ĭďèʼnţĩƒіểя": "ċơņťęאּť ỡƒ ţħíš - +この文字"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"ĭďèʼnţĩƒіểя": "ċơņťęאּť ỡƒ ţħíš - +この文字"}') + end + + it "should json parse the file" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(true) + allow(File).to receive(:read).with('C:/path/to/module/metadata.json').and_return('{"name": "spencer-science"}') + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(true) + allow(File).to receive(:read).with('/path/to/module/metadata.json').and_return('{"name": "spencer-science"}') + end + + result = subject.call(['science']) + expect(result['name']).to eq('spencer-science') + end + + it "should fail by default if there is no metadata.json" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(false) + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(false) + end + expect {subject.call(['science'])}.to raise_error(Puppet::ParseError) + end + + it "should return nil if user allows empty metadata.json" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(false) + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(false) + end + result = subject.call(['science', true]) + expect(result).to eq({}) + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/loadjson_spec.rb b/environments/prod/modules/stdlib/spec/functions/loadjson_spec.rb new file mode 100644 index 0000000..dbef805 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/loadjson_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'loadjson' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + + describe "when calling with valid arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"name": "puppetlabs-stdlib"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"name": "puppetlabs-stdlib"}') + end + + context 'when a non-existing file is specified' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesnotexist' + else + '/tmp/doesnotexist' + end + } + before { + allow(File).to receive(:exists?).with(filename).and_return(false).once + allow(PSON).to receive(:load).never + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + it { is_expected.to run.with_params(filename, {'đẽƒằưļŧ' => '٧ẵłựέ'}).and_return({'đẽƒằưļŧ' => '٧ẵłựέ'}) } + it { is_expected.to run.with_params(filename, {'デフォルト' => '値'}).and_return({'デフォルト' => '値'}) } + end + + context 'when an existing file is specified' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesexist' + else + '/tmp/doesexist' + end + } + let(:data) { { 'key' => 'value', 'ķęŷ' => 'νậŀųề', 'キー' => '値'} } + let(:json) { '{"key":"value", {"ķęŷ":"νậŀųề" }, {"キー":"値" }' } + before { + allow(File).to receive(:exists?).with(filename).and_return(true).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(PSON).to receive(:load).with(json).and_return(data).once + } + it { is_expected.to run.with_params(filename).and_return(data) } + end + + context 'when the file could not be parsed' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesexist' + else + '/tmp/doesexist' + end + } + let(:json) { '{"key":"value"}' } + before { + allow(File).to receive(:exists?).with(filename).and_return(true).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(PSON).to receive(:load).with(json).once.and_raise StandardError, 'Something terrible have happened!' + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/loadyaml_spec.rb b/environments/prod/modules/stdlib/spec/functions/loadyaml_spec.rb new file mode 100755 index 0000000..e9428e3 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/loadyaml_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'loadyaml' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + + context 'when a non-existing file is specified' do + let(:filename) { '/tmp/doesnotexist' } + before { + File.expects(:exists?).with(filename).returns(false).once + YAML.expects(:load_file).never + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + it { is_expected.to run.with_params(filename, {'đẽƒằưļŧ' => '٧ẵłựέ'}).and_return({'đẽƒằưļŧ' => '٧ẵłựέ'}) } + it { is_expected.to run.with_params(filename, {'デフォルト' => '値'}).and_return({'デフォルト' => '値'}) } + end + + context 'when an existing file is specified' do + let(:filename) { '/tmp/doesexist' } + let(:data) { { 'key' => 'value', 'ķęŷ' => 'νậŀųề', 'キー' => '値'} } + before { + File.expects(:exists?).with(filename).returns(true).once + YAML.expects(:load_file).with(filename).returns(data).once + } + it { is_expected.to run.with_params(filename).and_return(data) } + end + + context 'when the file could not be parsed' do + let(:filename) { '/tmp/doesexist' } + before { + File.expects(:exists?).with(filename).returns(true).once + YAML.stubs(:load_file).with(filename).once.raises StandardError, 'Something terrible have happened!' + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/lstrip_spec.rb b/environments/prod/modules/stdlib/spec/functions/lstrip_spec.rb new file mode 100755 index 0000000..a5a09ed --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/lstrip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'lstrip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params("\tone").and_return('one') } + it { is_expected.to run.with_params("\t one").and_return('one') } + it { is_expected.to run.with_params('one ').and_return('one ') } + it { is_expected.to run.with_params(' one ').and_return('one ') } + it { is_expected.to run.with_params(' one ').and_return('one ') } + it { is_expected.to run.with_params(' ǿňè ').and_return('ǿňè ') } + it { is_expected.to run.with_params("\tone ").and_return('one ') } + it { is_expected.to run.with_params("\t one ").and_return('one ') } + it { is_expected.to run.with_params("one \t").and_return("one \t") } + it { is_expected.to run.with_params(" one \t").and_return("one \t") } + it { is_expected.to run.with_params(" one \t").and_return("one \t") } + it { is_expected.to run.with_params("\tone \t").and_return("one \t") } + it { is_expected.to run.with_params("\t one \t").and_return("one \t") } + it { is_expected.to run.with_params(' o n e ').and_return('o n e ') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return('one ') } +end diff --git a/environments/prod/modules/stdlib/spec/functions/max_spec.rb b/environments/prod/modules/stdlib/spec/functions/max_spec.rb new file mode 100755 index 0000000..66fb0c8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/max_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'max' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(1, 2).and_return(2) } + it { is_expected.to run.with_params(1, 2, 3).and_return(3) } + it { is_expected.to run.with_params(3, 2, 1).and_return(3) } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params('one', 'two').and_return('two') } + it { is_expected.to run.with_params('one', 'two', 'three').and_return('two') } + it { is_expected.to run.with_params('three', 'two', 'one').and_return('two') } + + describe 'implementation artifacts' do + it { is_expected.to run.with_params(1, 'one').and_return('one') } + it { is_expected.to run.with_params('1', 'one').and_return('one') } + it { is_expected.to run.with_params('1.3e1', '1.4e0').and_return('1.4e0') } + it { is_expected.to run.with_params(1.3e1, 1.4e0).and_return(1.3e1) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/member_spec.rb b/environments/prod/modules/stdlib/spec/functions/member_spec.rb new file mode 100755 index 0000000..8988632 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/member_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'member' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], [], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([], '').and_return(false) } + it { is_expected.to run.with_params([], ['']).and_return(false) } + it { is_expected.to run.with_params([''], '').and_return(true) } + it { is_expected.to run.with_params([''], ['']).and_return(true) } + it { is_expected.to run.with_params([], 'one').and_return(false) } + it { is_expected.to run.with_params([], ['one']).and_return(false) } + it { is_expected.to run.with_params(['one'], 'one').and_return(true) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(true) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four'], ['four', 'two']).and_return(true) } + it { is_expected.to run.with_params(['ọאּẹ', 'ŧẅồ', 'ţҺŗęē', 'ƒơџŕ'], ['ƒơџŕ', 'ŧẅồ']).and_return(true) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four'], ['four', 'five']).and_return(false) } + it { is_expected.to run.with_params(['ọאּẹ', 'ŧẅồ', 'ţҺŗęē', 'ƒơџŕ'], ['ƒơџŕ', 'ƒί√ə']).and_return(false) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/merge_spec.rb b/environments/prod/modules/stdlib/spec/functions/merge_spec.rb new file mode 100755 index 0000000..7b53363 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/merge_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'merge' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, 'two').and_raise_error(Puppet::ParseError, /unexpected argument type String/) } + it { is_expected.to run.with_params({}, 1).and_raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) } + it { + pending 'should not special case this' + is_expected.to run.with_params({}).and_return({}) + } + it { is_expected.to run.with_params({}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}).and_return({}) } + describe 'should accept empty strings as puppet undef' do + it { is_expected.to run.with_params({}, '').and_return({}) } + end + it { is_expected.to run.with_params({ 'key' => 'value' }, {}).and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({}, { 'key' => 'value' }).and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({ 'key' => 'value1' }, { 'key' => 'value2' }).and_return({ 'key' => 'value2' }) } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1' }, { 'key2' => 'value2' }, { 'key3' => 'value3' }) \ + .and_return({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }) + } +end diff --git a/environments/prod/modules/stdlib/spec/functions/min_spec.rb b/environments/prod/modules/stdlib/spec/functions/min_spec.rb new file mode 100755 index 0000000..c840a72 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/min_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'min' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(1, 2).and_return(1) } + it { is_expected.to run.with_params(1, 2, 3).and_return(1) } + it { is_expected.to run.with_params(3, 2, 1).and_return(1) } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('one', 'two', 'three').and_return('one') } + it { is_expected.to run.with_params('three', 'two', 'one').and_return('one') } + + describe 'implementation artifacts' do + it { is_expected.to run.with_params(1, 'one').and_return(1) } + it { is_expected.to run.with_params('1', 'one').and_return('1') } + it { is_expected.to run.with_params('1.3e1', '1.4e0').and_return('1.3e1') } + it { is_expected.to run.with_params(1.3e1, 1.4e0).and_return(1.4e0) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/num2bool_spec.rb b/environments/prod/modules/stdlib/spec/functions/num2bool_spec.rb new file mode 100755 index 0000000..494afff --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/num2bool_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'num2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('abc').and_raise_error(Puppet::ParseError, /does not look like a number/) } + it { is_expected.to run.with_params(1).and_return(true) } + it { is_expected.to run.with_params('1').and_return(true) } + it { is_expected.to run.with_params(1.5).and_return(true) } + it { is_expected.to run.with_params('1.5').and_return(true) } + it { is_expected.to run.with_params(-1).and_return(false) } + it { is_expected.to run.with_params('-1').and_return(false) } + it { is_expected.to run.with_params(-1.5).and_return(false) } + it { is_expected.to run.with_params('-1.5').and_return(false) } + it { is_expected.to run.with_params(0).and_return(false) } + it { is_expected.to run.with_params('0').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params('[]').and_raise_error(Puppet::ParseError, /does not look like a number/) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params('{}').and_raise_error(Puppet::ParseError, /does not look like a number/) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/parsejson_spec.rb b/environments/prod/modules/stdlib/spec/functions/parsejson_spec.rb new file mode 100755 index 0000000..7b07e49 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/parsejson_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe 'parsejson' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should raise an error if called without any arguments' do + is_expected.to run.with_params(). + and_raise_error(/wrong number of arguments/i) + end + + context 'with correct JSON data' do + + it 'should be able to parse JSON data with a Hash' do + is_expected.to run.with_params('{"a":"1","b":"2"}'). + and_return({'a'=>'1', 'b'=>'2'}) + end + + it 'should be able to parse JSON data with an Array' do + is_expected.to run.with_params('["a","b","c"]'). + and_return(['a', 'b', 'c']) + end + + it 'should be able to parse empty JSON values' do + is_expected.to run.with_params('[]'). + and_return([]) + is_expected.to run.with_params('{}'). + and_return({}) + end + + it 'should be able to parse JSON data with a mixed structure' do + is_expected.to run.with_params('{"a":"1","b":2,"c":{"d":[true,false]}}'). + and_return({'a' =>'1', 'b' => 2, 'c' => { 'd' => [true, false] } }) + end + + it 'should be able to parse JSON data with a UTF8 and double byte characters' do + is_expected.to run.with_params('{"×":"これ","ý":"記号","です":{"©":["Á","ß"]}}'). + and_return({'×' =>'これ', 'ý' => '記号', 'です' => { '©' => ['Á', 'ß'] } }) + end + + it 'should not return the default value if the data was parsed correctly' do + is_expected.to run.with_params('{"a":"1"}', 'default_value'). + and_return({'a' => '1'}) + end + + end + + context 'with incorrect JSON data' do + it 'should raise an error with invalid JSON and no default' do + is_expected.to run.with_params(''). + and_raise_error(PSON::ParserError) + end + + it 'should support a structure for a default value' do + is_expected.to run.with_params('', {'a' => '1'}). + and_return({'a' => '1'}) + end + + ['', 1, 1.2, nil, true, false, [], {}, :yaml].each do |value| + it "should return the default value for an incorrect #{value.inspect} (#{value.class}) parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/parseyaml_spec.rb b/environments/prod/modules/stdlib/spec/functions/parseyaml_spec.rb new file mode 100755 index 0000000..c2a138c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/parseyaml_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'parseyaml' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should raise an error if called without any arguments' do + is_expected.to run.with_params(). + and_raise_error(/wrong number of arguments/i) + end + + context 'with correct YAML data' do + it 'should be able to parse a YAML data with a String' do + is_expected.to run.with_params('--- just a string'). + and_return('just a string') + is_expected.to run.with_params('just a string'). + and_return('just a string') + end + + it 'should be able to parse YAML data with a Hash' do + is_expected.to run.with_params("---\na: '1'\nb: '2'\n"). + and_return({'a' => '1', 'b' => '2'}) + end + + it 'should be able to parse YAML data with an Array' do + is_expected.to run.with_params("---\n- a\n- b\n- c\n"). + and_return(['a', 'b', 'c']) + end + + it 'should be able to parse YAML data with a mixed structure' do + is_expected.to run.with_params("---\na: '1'\nb: 2\nc:\n d:\n - :a\n - true\n - false\n"). + and_return({'a' => '1', 'b' => 2, 'c' => {'d' => [:a, true, false]}}) + end + + it 'should be able to parse YAML data with a UTF8 and double byte characters' do + is_expected.to run.with_params("---\na: ×\nこれ: 記号\nです:\n ©:\n - Á\n - ß\n"). + and_return({"a"=>"×", "これ"=>"記号", "です"=>{"©"=>["Á", "ß"]} }) + end + + it 'should not return the default value if the data was parsed correctly' do + is_expected.to run.with_params("---\na: '1'\n", 'default_value'). + and_return({'a' => '1'}) + end + + end + + context 'on a modern ruby', :unless => RUBY_VERSION == '1.8.7' do + it 'should raise an error with invalid YAML and no default' do + is_expected.to run.with_params('["one"'). + and_raise_error(Psych::SyntaxError) + end + end + + context 'when running on ruby 1.8.7, which does not have Psych', :if => RUBY_VERSION == '1.8.7' do + it 'should raise an error with invalid YAML and no default' do + is_expected.to run.with_params('["one"'). + and_raise_error(ArgumentError) + end + end + + context 'with incorrect YAML data' do + it 'should support a structure for a default value' do + is_expected.to run.with_params('', {'a' => '1'}). + and_return({'a' => '1'}) + end + + [1, 1.2, nil, true, false, [], {}, :yaml].each do |value| + it "should return the default value for an incorrect #{value.inspect} (#{value.class}) parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + ['---', '...', '*8', ''].each do |value| + it "should return the default value for an incorrect #{value.inspect} string parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + end + + end + +end diff --git a/environments/prod/modules/stdlib/spec/functions/pick_default_spec.rb b/environments/prod/modules/stdlib/spec/functions/pick_default_spec.rb new file mode 100755 index 0000000..a7ffc86 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/pick_default_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'pick_default' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::Error, /Must receive at least one argument/) } + + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('ớņệ', 'ťωơ').and_return('ớņệ') } + it { is_expected.to run.with_params('', 'two').and_return('two') } + it { is_expected.to run.with_params(:undef, 'two').and_return('two') } + it { is_expected.to run.with_params(:undefined, 'two').and_return('two') } + it { is_expected.to run.with_params(nil, 'two').and_return('two') } + + [ '', :undef, :undefined, nil, {}, [], 1, 'default' ].each do |value| + describe "when providing #{value.inspect} as default" do + it { is_expected.to run.with_params('one', value).and_return('one') } + it { is_expected.to run.with_params('ớņệ', value).and_return('ớņệ') } + it { is_expected.to run.with_params([], value).and_return([]) } + it { is_expected.to run.with_params({}, value).and_return({}) } + it { is_expected.to run.with_params(value, value).and_return(value) } + it { is_expected.to run.with_params(:undef, value).and_return(value) } + it { is_expected.to run.with_params(:undefined, value).and_return(value) } + it { is_expected.to run.with_params(nil, value).and_return(value) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/pick_spec.rb b/environments/prod/modules/stdlib/spec/functions/pick_spec.rb new file mode 100755 index 0000000..438553b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/pick_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'pick' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /must receive at least one non empty value/) } + it { is_expected.to run.with_params('', nil, :undef, :undefined).and_raise_error(Puppet::ParseError, /must receive at least one non empty value/) } + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('', 'two').and_return('two') } + it { is_expected.to run.with_params(:undef, 'two').and_return('two') } + it { is_expected.to run.with_params(:undefined, 'two').and_return('two') } + it { is_expected.to run.with_params(nil, 'two').and_return('two') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(nil, 'このテキスト').and_return('このテキスト') } + it { is_expected.to run.with_params('', 'ŝẳмрłề џţƒ8 ţẽם', 'このテキスト').and_return('ŝẳмрłề џţƒ8 ţẽם') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/prefix_spec.rb b/environments/prod/modules/stdlib/spec/functions/prefix_spec.rb new file mode 100755 index 0000000..5510c58 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/prefix_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'prefix' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], 'a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /expected first argument to be an Array or a Hash/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /expected second argument to be a String/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one', 2]).and_return(['one', '2']) } + it { is_expected.to run.with_params(['ớņệ', 2]).and_return(['ớņệ', '2']) } + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([''], '').and_return(['']) } + it { is_expected.to run.with_params(['one'], 'pre').and_return(['preone']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'pre').and_return(['preone', 'pretwo', 'prethree']) } + it { is_expected.to run.with_params({}).and_return({}) } + it { is_expected.to run.with_params({ 'key1' => 'value1', 2 => 3}).and_return({ 'key1' => 'value1', '2' => 3 }) } + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({ 'key' => 'value' }, '').and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({ 'key' => 'value' }, 'pre').and_return({ 'prekey' => 'value' }) } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }, 'pre') \ + .and_return({ 'prekey1' => 'value1', 'prekey2' => 'value2', 'prekey3' => 'value3' }) + } +end diff --git a/environments/prod/modules/stdlib/spec/functions/private_spec.rb b/environments/prod/modules/stdlib/spec/functions/private_spec.rb new file mode 100644 index 0000000..a13be64 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/private_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'private' do + it 'should issue a warning' do + scope.expects(:warning).with("private() DEPRECATED: This function will cease to function on Puppet 4; please use assert_private() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + begin + subject.call [] + rescue + # ignore this + end + end + + context "when called from inside module" do + it "should not fail" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('foo') + expect { + subject.call [] + }.not_to raise_error + end + end + + context "with an explicit failure message" do + it "prints the failure message on error" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + expect { + subject.call ['failure message!'] + }.to raise_error Puppet::ParseError, /failure message!/ + end + end + + context "when called from private class" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('hostclass') + expect { + subject.call [] + }.to raise_error Puppet::ParseError, /Class foo::baz is private/ + end + end + + context "when called from private definition" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('definition') + expect { + subject.call [] + }.to raise_error Puppet::ParseError, /Definition foo::baz is private/ + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/pw_hash_spec.rb b/environments/prod/modules/stdlib/spec/functions/pw_hash_spec.rb new file mode 100644 index 0000000..df5348c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/pw_hash_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe 'pw_hash' do + + it { is_expected.not_to eq(nil) } + + context 'when there are less than 3 arguments' do + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password').and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 'sha-256').and_raise_error(ArgumentError, /wrong number of arguments/i) } + end + + context 'when there are more than 3 arguments' do + it { is_expected.to run.with_params('password', 'sha-256', 'salt', 'extra').and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 'sha-256', 'salt', 'extra', 'extra').and_raise_error(ArgumentError, /wrong number of arguments/i) } + end + + context 'when the first argument is not a string' do + it { is_expected.to run.with_params([], 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params({}, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params(1, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params(true, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + end + + context 'when the first argument is undefined' do + it { is_expected.to run.with_params('', 'sha-256', 'salt').and_return(nil) } + it { is_expected.to run.with_params(nil, 'sha-256', 'salt').and_return(nil) } + end + + context 'when the second argument is not a string' do + it { is_expected.to run.with_params('password', [], 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', {}, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', 1, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', true, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + end + + context 'when the second argument is not one of the supported hashing algorithms' do + it { is_expected.to run.with_params('password', 'no such algo', 'salt').and_raise_error(ArgumentError, /is not a valid hash type/) } + end + + context 'when the third argument is not a string' do + it { is_expected.to run.with_params('password', 'sha-256', []).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', {}).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', 1).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', true).and_raise_error(ArgumentError, /third argument must be a string/) } + end + + context 'when the third argument is empty' do + it { is_expected.to run.with_params('password', 'sha-512', '').and_raise_error(ArgumentError, /third argument must not be empty/) } + end + + context 'when the third argument contains invalid characters' do + it { is_expected.to run.with_params('password', 'sha-512', 'one%').and_raise_error(ArgumentError, /characters in salt must be in the set/) } + end + + context 'when running on a platform with a weak String#crypt implementation' do + before(:each) { allow_any_instance_of(String).to receive(:crypt).with('$1$1').and_return('a bad hash') } + + it { is_expected.to run.with_params('password', 'sha-512', 'salt').and_raise_error(Puppet::ParseError, /system does not support enhanced salts/) } + end + + if RUBY_PLATFORM == 'java' or 'test'.crypt('$1$1') == '$1$1$Bp8CU9Oujr9SSEw53WV6G.' + describe "on systems with enhanced salts support" do + it { is_expected.to run.with_params('password', 'md5', 'salt').and_return('$1$salt$qJH7.N4xYta3aEG/dfqo/0') } + it { is_expected.to run.with_params('password', 'sha-256', 'salt').and_return('$5$salt$Gcm6FsVtF/Qa77ZKD.iwsJlCVPY0XSMgLJL0Hnww/c1') } + it { is_expected.to run.with_params('password', 'sha-512', 'salt').and_return('$6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g.') } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/range_spec.rb b/environments/prod/modules/stdlib/spec/functions/range_spec.rb new file mode 100755 index 0000000..ca569d5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/range_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe 'range' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation in puppet3', :unless => RSpec.configuration.puppet_future do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the third.") + is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('1..2..3').and_raise_error(Puppet::ParseError, /Unable to compute range/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /Unknown range format/i) } + end + + describe 'signature validation in puppet4', :if => RSpec.configuration.puppet_future do + it { pending "the puppet 4 implementation"; is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params('').and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params({}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params([]).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(1, 2, 'foo').and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, []).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, {}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, true).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params('1..2..3').and_raise_error(ArgumentError) } + end + + context 'with characters as bounds' do + it { is_expected.to run.with_params('d', 'a').and_return([]) } + it { is_expected.to run.with_params('a', 'a').and_return(['a']) } + it { is_expected.to run.with_params('a', 'b').and_return(['a', 'b']) } + it { is_expected.to run.with_params('a', 'd').and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', 1).and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', '1').and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', 2).and_return(['a', 'c']) } + it { is_expected.to run.with_params('a', 'd', -2).and_return(['a', 'c']) } + it { is_expected.to run.with_params('a', 'd', 3).and_return(['a', 'd']) } + it { is_expected.to run.with_params('a', 'd', 4).and_return(['a']) } + end + + context 'with strings as bounds' do + it { is_expected.to run.with_params('onea', 'oned').and_return(['onea', 'oneb', 'onec', 'oned']) } + it { is_expected.to run.with_params('two', 'one').and_return([]) } + it { is_expected.to run.with_params('true', 'false').and_return([]) } + it { is_expected.to run.with_params('false', 'true').and_return(['false']) } + end + + context 'with integers as bounds' do + it { is_expected.to run.with_params(4, 1).and_return([]) } + it { is_expected.to run.with_params(1, 1).and_return([1]) } + it { is_expected.to run.with_params(1, 2).and_return([1, 2]) } + it { is_expected.to run.with_params(1, 4).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, 1).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, '1').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, 2).and_return([1, 3]) } + it { is_expected.to run.with_params(1, 4, -2).and_return([1, 3]) } + it { is_expected.to run.with_params(1, 4, 3).and_return([1, 4]) } + it { is_expected.to run.with_params(1, 4, 4).and_return([1]) } + end + + context 'with integers as strings as bounds' do + it { is_expected.to run.with_params('4', '1').and_return([]) } + it { is_expected.to run.with_params('1', '1').and_return([1]) } + it { is_expected.to run.with_params('1', '2').and_return([1, 2]) } + it { is_expected.to run.with_params('1', '4').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', 1).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', '1').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', 2).and_return([1, 3]) } + it { is_expected.to run.with_params('1', '4', -2).and_return([1, 3]) } + it { is_expected.to run.with_params('1', '4', 3).and_return([1, 4]) } + it { is_expected.to run.with_params('1', '4', 4).and_return([1]) } + end + + context 'with prefixed numbers as strings as bounds' do + it { is_expected.to run.with_params('host01', 'host04').and_return(['host01', 'host02', 'host03', 'host04']) } + it { is_expected.to run.with_params('01', '04').and_return([1, 2, 3, 4]) } + end + + context 'with prefixed numbers as utf8 strings as bounds' do + it { is_expected.to run.with_params('ħөŝŧ01', 'ħөŝŧ04').and_return(['ħөŝŧ01', 'ħөŝŧ02', 'ħөŝŧ03', 'ħөŝŧ04']) } + end + + context 'with prefixed numbers as double byte character strings as bounds' do + it { is_expected.to run.with_params('ホスト01', 'ホスト04').and_return(['ホスト01', 'ホスト02', 'ホスト03', 'ホスト04']) } + end + + context 'with dash-range syntax' do + it { is_expected.to run.with_params('4-1').and_return([]) } + it { is_expected.to run.with_params('1-1').and_return([1]) } + it { is_expected.to run.with_params('1-2').and_return([1, 2]) } + it { is_expected.to run.with_params('1-4').and_return([1, 2, 3, 4]) } + end + + context 'with two-dot-range syntax' do + it { is_expected.to run.with_params('4..1').and_return([]) } + it { is_expected.to run.with_params('1..1').and_return([1]) } + it { is_expected.to run.with_params('1..2').and_return([1, 2]) } + it { is_expected.to run.with_params('1..4').and_return([1, 2, 3, 4]) } + end + + context 'with three-dot-range syntax' do + it { is_expected.to run.with_params('4...1').and_return([]) } + it { is_expected.to run.with_params('1...1').and_return([]) } + it { is_expected.to run.with_params('1...2').and_return([1]) } + it { is_expected.to run.with_params('1...3').and_return([1, 2]) } + it { is_expected.to run.with_params('1...5').and_return([1, 2, 3, 4]) } + end + + describe 'when passing mixed arguments as bounds' do + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params('0', 'a').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params(0, 'a').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params('h0', 'ha').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/regexpescape_spec.rb b/environments/prod/modules/stdlib/spec/functions/regexpescape_spec.rb new file mode 100644 index 0000000..36dbe70 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/regexpescape_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'regexpescape' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + describe 'handling normal strings' do + it 'should call ruby\'s Regexp.escape function' do + Regexp.expects(:escape).with('regexp_string').returns('escaped_regexp_string').once + is_expected.to run.with_params('regexp_string').and_return('escaped_regexp_string') + end + end + + describe 'handling classes derived from String' do + it 'should call ruby\'s Regexp.escape function' do + regexp_string = AlsoString.new('regexp_string') + Regexp.expects(:escape).with(regexp_string).returns('escaped_regexp_string').once + is_expected.to run.with_params(regexp_string).and_return("escaped_regexp_string") + end + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one*', "two"]).and_return(['one\*', "two"]) } + it { is_expected.to run.with_params(['one*', 1, true, {}, "two"]).and_return(['one\*', 1, true, {}, "two"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['ŏʼnε*']).and_return(['ŏʼnε\*']) } + it { is_expected.to run.with_params(['インターネット*']).and_return(['インターネット\*']) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/reject_spec.rb b/environments/prod/modules/stdlib/spec/functions/reject_spec.rb new file mode 100755 index 0000000..86db7c7 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/reject_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'reject' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'pattern', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { + pending("reject does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { + pending("reject does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params(1, 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 't(wo|hree)').and_return(['one']) } + it { is_expected.to run.with_params(['όʼnệ', 'ţщồ', 'ţңяέέ'], 'ţ(щồ|ңяέέ)').and_return(['όʼnệ']) } +end diff --git a/environments/prod/modules/stdlib/spec/functions/reverse_spec.rb b/environments/prod/modules/stdlib/spec/functions/reverse_spec.rb new file mode 100755 index 0000000..79bc0ad --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/reverse_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'reverse' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(['three', 'two', 'one']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(['four', 'three', 'two', 'one']) } + it { is_expected.to run.with_params(['ổňë', 'ťŵọ', 'ŧңяəė', 'ƒŏůŗ']).and_return(['ƒŏůŗ', 'ŧңяəė', 'ťŵọ', 'ổňë']) } + + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('abc').and_return('cba') } + it { is_expected.to run.with_params('abcd').and_return('dcba') } + it { is_expected.to run.with_params('āβćđ').and_return('đćβā') } + + context 'when using a class extending String' do + it 'should call its reverse method' do + value = AlsoString.new('asdfghjkl') + value.expects(:reverse).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/rstrip_spec.rb b/environments/prod/modules/stdlib/spec/functions/rstrip_spec.rb new file mode 100755 index 0000000..a7663e2 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/rstrip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'rstrip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return(' one') } + it { is_expected.to run.with_params(' one').and_return(' one') } + it { is_expected.to run.with_params("\tone").and_return("\tone") } + it { is_expected.to run.with_params("\t one").and_return("\t one") } + it { is_expected.to run.with_params('one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return(' one') } + it { is_expected.to run.with_params(' one ').and_return(' one') } + it { is_expected.to run.with_params(' ǿňè ').and_return(' ǿňè') } + it { is_expected.to run.with_params("\tone ").and_return("\tone") } + it { is_expected.to run.with_params("\t one ").and_return("\t one") } + it { is_expected.to run.with_params("one\t").and_return('one') } + it { is_expected.to run.with_params(" one\t").and_return(' one') } + it { is_expected.to run.with_params(" one\t").and_return(' one') } + it { is_expected.to run.with_params("\tone\t").and_return("\tone") } + it { is_expected.to run.with_params("\t one\t").and_return("\t one") } + it { is_expected.to run.with_params(' o n e ').and_return(' o n e') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return(' one') } +end diff --git a/environments/prod/modules/stdlib/spec/functions/seeded_rand_spec.rb b/environments/prod/modules/stdlib/spec/functions/seeded_rand_spec.rb new file mode 100644 index 0000000..ac108f4 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/seeded_rand_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe 'seeded_rand' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1.5, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(-10, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("-10", '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("string", '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params([], '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params({}, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, /second argument must be a string/) } + + it "provides a random number strictly less than the given max" do + expect(seeded_rand(3, 'seed')).to satisfy {|n| n.to_i < 3 } + end + + it "provides a random number greater or equal to zero" do + expect(seeded_rand(3, 'seed')).to satisfy {|n| n.to_i >= 0 } + end + + it "provides the same 'random' value on subsequent calls for the same host" do + expect(seeded_rand(10, 'seed')).to eql(seeded_rand(10, 'seed')) + end + + it "allows seed to control the random value on a single host" do + first_random = seeded_rand(1000, 'seed1') + second_different_random = seeded_rand(1000, 'seed2') + + expect(first_random).not_to eql(second_different_random) + end + + it "should not return different values for different hosts" do + val1 = seeded_rand(1000, 'foo', :host => "first.host.com") + val2 = seeded_rand(1000, 'foo', :host => "second.host.com") + + expect(val1).to eql(val2) + end + + def seeded_rand(max, seed, args = {}) + host = args[:host] || '127.0.0.1' + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn", {}).returns(host) + + scope.function_seeded_rand([max, seed]) + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(1000, 'ǿňè')} + it { is_expected.to run.with_params(1000, '文字列')} + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/shell_escape_spec.rb b/environments/prod/modules/stdlib/spec/functions/shell_escape_spec.rb new file mode 100644 index 0000000..77917dd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/shell_escape_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe 'shell_escape' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'stringification' do + it { is_expected.to run.with_params(10).and_return('10') } + it { is_expected.to run.with_params(false).and_return('false') } + end + + describe 'escaping' do + it { is_expected.to run.with_params('foo').and_return('foo') } + it { is_expected.to run.with_params('foo bar').and_return('foo\ bar') } + it { is_expected.to run.with_params('~`!@#$%^&*()_+-=[]\{}|;\':",./<>?') + .and_return('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?') } + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('スペー スを含むテ キスト').and_return('\\ス\\ペ\\ー\\ \\ス\\を\\含\\む\\テ\\ \\ \\キ\\ス\\ト') } + it { is_expected.to run.with_params('μťƒ 8 ŧĕχť').and_return('\\μ\\ť\\ƒ\\ 8\\ \\ \\ŧ\\ĕ\\χ\\ť') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/shell_join_spec.rb b/environments/prod/modules/stdlib/spec/functions/shell_join_spec.rb new file mode 100644 index 0000000..46305bf --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/shell_join_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe 'shell_join' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(['foo'], ['bar']).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo').and_raise_error(Puppet::ParseError, /is not an Array/i) } + end + + describe 'shell argument joining' do + it { is_expected.to run.with_params(['foo']).and_return('foo') } + it { is_expected.to run.with_params(['foo', 'bar']).and_return('foo bar') } + it { is_expected.to run.with_params(['foo', 'bar baz']).and_return('foo bar\ baz') } + it { is_expected.to run.with_params(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?']) + .and_return('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['μťƒ', '8', 'ŧĕχť']).and_return('\\μ\\ť\\ƒ 8 \\ŧ\\ĕ\\χ\\ť') } + it { is_expected.to run.with_params(['スペー', 'スを含むテ', ' キスト']).and_return('\\ス\\ペ\\ー \\ス\\を\\含\\む\\テ \\ \\キ\\ス\\ト') } + end + end + + describe 'stringification' do + it { is_expected.to run.with_params([10, false, 'foo']).and_return('10 false foo') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/shell_split_spec.rb b/environments/prod/modules/stdlib/spec/functions/shell_split_spec.rb new file mode 100644 index 0000000..f8f9c90 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/shell_split_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'shell_split' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'stringification' do + it { is_expected.to run.with_params(10).and_return(['10']) } + it { is_expected.to run.with_params(false).and_return(['false']) } + end + + describe 'shell line spliting' do + it { is_expected.to run.with_params('foo').and_return(['foo']) } + it { is_expected.to run.with_params('foo bar').and_return(['foo', 'bar']) } + it { is_expected.to run.with_params('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?') + .and_return(['~`!@#$%^&*()_+-=[]\{}|;\':",./<>?']) } + it { is_expected.to run.with_params('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?') + .and_return(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?']) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('\\μ\\ť\\ƒ 8 \\ŧ\\ĕ\\χ\\ť').and_return(['μťƒ', '8', 'ŧĕχť']) } + it { is_expected.to run.with_params('\\ス\\ペ\\ー \\ス\\を\\含\\む\\テ \\ \\キ\\ス\\ト').and_return(['スペー', 'スを含むテ', ' キスト']) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/shuffle_spec.rb b/environments/prod/modules/stdlib/spec/functions/shuffle_spec.rb new file mode 100755 index 0000000..4673daa --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/shuffle_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'shuffle' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + + context 'when running with a specific seed' do + # make tests deterministic + before(:each) { srand(2) } + + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(['two', 'one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(['four', 'three', 'two', 'one']) } + + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('abc').and_return('bac') } + it { is_expected.to run.with_params('abcd').and_return('dcba') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('ůţƒ8 ŧέχŧ şŧґíńģ').and_return('ģńş ůχţέƒŧí8ґŧŧ ') } + it { is_expected.to run.with_params('日本語の文字列').and_return('字本日語文列の') } + end + + context 'when using a class extending String' do + it { is_expected.to run.with_params(AlsoString.new('asdfghjkl')).and_return('lkhdsfajg') } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/size_spec.rb b/environments/prod/modules/stdlib/spec/functions/size_spec.rb new file mode 100755 index 0000000..2047423 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/size_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'size' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Unknown type given/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Unknown type given/) } + it { is_expected.to run.with_params('1').and_raise_error(Puppet::ParseError, /Requires either string, array or hash to work/) } + it { is_expected.to run.with_params('1.0').and_raise_error(Puppet::ParseError, /Requires either string, array or hash to work/) } + it { is_expected.to run.with_params([]).and_return(0) } + it { is_expected.to run.with_params(['a']).and_return(1) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(3) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(4) } + + it { is_expected.to run.with_params({}).and_return(0) } + it { is_expected.to run.with_params({'1' => '2'}).and_return(1) } + it { is_expected.to run.with_params({'1' => '2', '4' => '4'}).and_return(2) } + it { is_expected.to run.with_params({'€' => '@', '竹' => 'ǿňè'}).and_return(2) } + + it { is_expected.to run.with_params('').and_return(0) } + it { is_expected.to run.with_params('a').and_return(1) } + it { is_expected.to run.with_params('abc').and_return(3) } + it { is_expected.to run.with_params('abcd').and_return(4) } + it { is_expected.to run.with_params('万').and_return(1) } + it { is_expected.to run.with_params('āβćđ').and_return(4) } + + context 'when using a class extending String' do + it 'should call its size method' do + value = AlsoString.new('asdfghjkl') + value.expects(:size).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/sort_spec.rb b/environments/prod/modules/stdlib/spec/functions/sort_spec.rb new file mode 100755 index 0000000..9abd039 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/sort_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'sort' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { pending('stricter input checking'); is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /requires string or array/) } + it { pending('stricter input checking'); is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /requires string or array/) } + it { pending('stricter input checking'); is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /requires string or array/) } + end + + context 'when called with an array' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['c', 'b', 'a']).and_return(['a', 'b', 'c']) } + end + + context 'when called with a string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('cbda').and_return('abcd') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/squeeze_spec.rb b/environments/prod/modules/stdlib/spec/functions/squeeze_spec.rb new file mode 100755 index 0000000..b267d9a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/squeeze_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe 'squeeze' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(NoMethodError) } + it { is_expected.to run.with_params({}).and_raise_error(NoMethodError) } + it { is_expected.to run.with_params(true).and_raise_error(NoMethodError) } + + context 'when squeezing a single string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaa').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaa', 'a').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaabbbbbbbbbbcccccccccc', 'b-c').and_return('aaaaaaaaabc') } + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('ậậậậậậậậậậậậậậậậậậậậ').and_return('ậ') } + it { is_expected.to run.with_params('語語語語語語語', '語').and_return('語') } + it { is_expected.to run.with_params('ậậậậậậậậậậậậậậậậậ語語語語©©©©©', '©').and_return('ậậậậậậậậậậậậậậậậậ語語語語©') } + end + + context 'when squeezing values in an array' do + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc']) \ + .and_return( ['', 'a', 'a', 'abc']) + } + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc'], 'a') \ + .and_return( ['', 'a', 'a', 'abbbbbbbbbbcccccccccc']) + } + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc'], 'b-c') \ + .and_return( ['', 'a', 'aaaaaaaaa', 'aaaaaaaaabc']) + } + end + + context 'when using a class extending String' do + it 'should call its squeeze method' do + value = AlsoString.new('aaaaaaaaa') + value.expects(:squeeze).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/str2bool_spec.rb b/environments/prod/modules/stdlib/spec/functions/str2bool_spec.rb new file mode 100755 index 0000000..7d8c47c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/str2bool_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'str2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('true', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Unknown type of boolean given/) } + + describe 'when testing values that mean "true"' do + [ 'TRUE','1', 't', 'y', 'true', 'yes', true ].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing values that mean "false"' do + [ 'FALSE','', '0', 'f', 'n', 'false', 'no', false, 'undef', 'undefined' ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/str2saltedsha512_spec.rb b/environments/prod/modules/stdlib/spec/functions/str2saltedsha512_spec.rb new file mode 100755 index 0000000..2e1e818 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/str2saltedsha512_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'str2saltedsha512' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires a String argument/) } + + context 'when running with a specific seed' do + # make tests deterministic + before(:each) { srand(2) } + + it { is_expected.to run.with_params('').and_return('0f8a612f4eeed08e47b3875d00f33c5688f7926298f2d9b5fe19d1323f910bc78b6f7b5892596d2fabaa65e7a8d99b3768c102610cf0432c4827eee01f09451e3fae4f7a') } + it { is_expected.to run.with_params('password').and_return('0f8a612f43134376566c5707718d600effcfb17581fc9d3fa64d7f447dfda317c174ffdb498d2c5bd5c2075dab41c9d7ada5afbdc6b55354980eb5ba61802371e6b64956') } + it { is_expected.to run.with_params('verylongpassword').and_return('0f8a612f7a448537540e062daa8621f9bae326ca8ccb899e1bdb10e7c218cebfceae2530b856662565fdc4d81e986fc50cfbbc46d50436610ed9429ff5e43f2c45b5d039') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/strftime_spec.rb b/environments/prod/modules/stdlib/spec/functions/strftime_spec.rb new file mode 100755 index 0000000..e76774a --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/strftime_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'strftime' do + it "should exist" do + expect(Puppet::Parser::Functions.function("strftime")).to eq("function_strftime") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_strftime([]) }.to( raise_error(Puppet::ParseError)) + end + + it "using %s should be higher then when I wrote this test" do + result = scope.function_strftime(["%s"]) + expect(result.to_i).to(be > 1311953157) + end + + it "using %s should be lower then 1.5 trillion" do + result = scope.function_strftime(["%s"]) + expect(result.to_i).to(be < 1500000000) + end + + it "should return a date when given %Y-%m-%d" do + result = scope.function_strftime(["%Y-%m-%d"]) + expect(result).to match(/^\d{4}-\d{2}-\d{2}$/) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/strip_spec.rb b/environments/prod/modules/stdlib/spec/functions/strip_spec.rb new file mode 100755 index 0000000..18e943d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/strip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'strip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params("\tone").and_return('one') } + it { is_expected.to run.with_params("\t one").and_return('one') } + it { is_expected.to run.with_params('one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return('one') } + it { is_expected.to run.with_params("\tone ").and_return('one') } + it { is_expected.to run.with_params("\t one ").and_return('one') } + it { is_expected.to run.with_params("one \t").and_return('one') } + it { is_expected.to run.with_params(" one \t").and_return('one') } + it { is_expected.to run.with_params(" one \t").and_return('one') } + it { is_expected.to run.with_params("\tone \t").and_return('one') } + it { is_expected.to run.with_params("\t one \t").and_return('one') } + it { is_expected.to run.with_params(' o n e ').and_return('o n e') } + it { is_expected.to run.with_params(' ỏŋέ ').and_return('ỏŋέ') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return('one') } +end diff --git a/environments/prod/modules/stdlib/spec/functions/suffix_spec.rb b/environments/prod/modules/stdlib/spec/functions/suffix_spec.rb new file mode 100755 index 0000000..e0eafb1 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/suffix_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'suffix' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], 'a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /expected first argument to be an Array/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /expected second argument to be a String/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one', 2]).and_return(['one', '2']) } + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([''], '').and_return(['']) } + it { is_expected.to run.with_params(['one'], 'post').and_return(['onepost']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'post').and_return(['onepost', 'twopost', 'threepost']) } + it { is_expected.to run.with_params(['ỗńέ', 'ťשׂǿ', 'ŧҺř℮ə'], 'рổŝţ').and_return(['ỗńέрổŝţ', 'ťשׂǿрổŝţ', 'ŧҺř℮əрổŝţ']) } + + it { + is_expected.to run.with_params({}).and_return({}) + } + it { + is_expected.to run.with_params({ 'key1' => 'value1', 2 => 3}).and_return({ 'key1' => 'value1', '2' => 3 }) + } + it { + is_expected.to run.with_params({}, '').and_return({}) + } + it { + is_expected.to run.with_params({ 'key' => 'value' }, '').and_return({ 'key' => 'value' }) + } + it { + is_expected.to run.with_params({ 'key' => 'value' }, 'post').and_return({ 'keypost' => 'value' }) + } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }, 'post') \ + .and_return({ 'key1post' => 'value1', 'key2post' => 'value2', 'key3post' => 'value3' }) + } +end diff --git a/environments/prod/modules/stdlib/spec/functions/swapcase_spec.rb b/environments/prod/modules/stdlib/spec/functions/swapcase_spec.rb new file mode 100755 index 0000000..c175a15 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/swapcase_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'swapcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + describe 'with strings as inputs' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('one').and_return('ONE') } + it { is_expected.to run.with_params('ONE').and_return('one') } + it { is_expected.to run.with_params('oNe').and_return('OnE') } + end + describe 'with arrays as inputs' do + it { is_expected.to run.with_params([]).and_return([]) } + describe 'only containing strings' do + it { is_expected.to run.with_params(['']).and_return(['']) } + it { is_expected.to run.with_params(['one']).and_return(['ONE']) } + it { is_expected.to run.with_params(['ONE']).and_return(['one']) } + it { is_expected.to run.with_params(['oNe']).and_return(['OnE']) } + it { is_expected.to run.with_params(['one', 'ONE']).and_return(['ONE', 'one']) } + it { is_expected.to run.with_params(['ONE', 'OnE']).and_return(['one', 'oNe']) } + it { is_expected.to run.with_params(['oNe', 'one']).and_return(['OnE', 'ONE']) } + end + describe 'containing mixed types' do + it { is_expected.to run.with_params(['OnE', {}]).and_return(['oNe', {}]) } + it { is_expected.to run.with_params(['OnE', 1]).and_return(['oNe', 1]) } + it { is_expected.to run.with_params(['OnE', []]).and_return(['oNe', []]) } + it { is_expected.to run.with_params(['OnE', ['two']]).and_return(['oNe', ['two']]) } + end + end + it "should accept objects which extend String" do + is_expected.to run.with_params(AlsoString.new("OnE")).and_return('oNe') + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/time_spec.rb b/environments/prod/modules/stdlib/spec/functions/time_spec.rb new file mode 100755 index 0000000..d157939 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/time_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'time' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params('a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context 'when running at a specific time' do + before(:each) { + # get a value before stubbing the function + test_time = Time.utc(2006, 10, 13, 8, 15, 11) + Time.expects(:new).with().returns(test_time).once + } + it { is_expected.to run.with_params().and_return(1160727311) } + it { is_expected.to run.with_params('').and_return(1160727311) } + it { is_expected.to run.with_params([]).and_return(1160727311) } + it { is_expected.to run.with_params({}).and_return(1160727311) } + it { is_expected.to run.with_params('foo').and_return(1160727311) } + it { is_expected.to run.with_params('UTC').and_return(1160727311) } + it { is_expected.to run.with_params('America/New_York').and_return(1160727311) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/to_bytes_spec.rb b/environments/prod/modules/stdlib/spec/functions/to_bytes_spec.rb new file mode 100755 index 0000000..2be23ff --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/to_bytes_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'to_bytes' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('1', 'extras').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(TypeError, /(can't convert|no implicit conversion of) Array (in)?to String/) } + it { is_expected.to run.with_params({}).and_raise_error(TypeError, /(can't convert|no implicit conversion of) Hash (in)?to String/) } + it { is_expected.to run.with_params(true).and_raise_error(TypeError, /(can't convert|no implicit conversion of) (TrueClass|true) (in)?to String/) } + + describe 'when passing numbers' do + it { is_expected.to run.with_params(0).and_return(0) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(-1).and_return(-1) } + it { is_expected.to run.with_params(1.1).and_return(1.1) } + it { is_expected.to run.with_params(-1.1).and_return(-1.1) } + end + + describe 'when passing numbers as strings' do + describe 'without a unit' do + it { is_expected.to run.with_params('1').and_return(1) } + it { is_expected.to run.with_params('-1').and_return(-1) } + # these are so wrong + it { is_expected.to run.with_params('1.1').and_return(1) } + it { is_expected.to run.with_params('-1.1').and_return(-1) } + end + + describe 'with a unit' do + it { is_expected.to run.with_params('1k').and_return(1024) } + it { is_expected.to run.with_params('-1kB').and_return(-1024) } + it { is_expected.to run.with_params('1k').and_return(1024) } + it { is_expected.to run.with_params('1M').and_return(1024*1024) } + it { is_expected.to run.with_params('1G').and_return(1024*1024*1024) } + it { is_expected.to run.with_params('1T').and_return(1024*1024*1024*1024) } + it { is_expected.to run.with_params('1P').and_return(1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('1E').and_return(1024*1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('1.5e3M').and_return(1572864000) } + + it { is_expected.to run.with_params('4k').and_return(4*1024) } + it { is_expected.to run.with_params('-4kB').and_return(4*-1024) } + it { is_expected.to run.with_params('4k').and_return(4*1024) } + it { is_expected.to run.with_params('4M').and_return(4*1024*1024) } + it { is_expected.to run.with_params('4G').and_return(4*1024*1024*1024) } + it { is_expected.to run.with_params('4T').and_return(4*1024*1024*1024*1024) } + it { is_expected.to run.with_params('4P').and_return(4*1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('4E').and_return(4*1024*1024*1024*1024*1024*1024) } + + # these are so wrong + it { is_expected.to run.with_params('1.0001 k').and_return(1024) } + it { is_expected.to run.with_params('-1.0001 kB').and_return(-1024) } + end + + describe 'with a unknown unit' do + it { is_expected.to run.with_params('1KB').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1K').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1mb').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1m').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1%').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1 p').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + end + end + + # these are so wrong + describe 'when passing random stuff' do + it { is_expected.to run.with_params('-1....1').and_return(-1) } + it { is_expected.to run.with_params('-1.e.e.e.1').and_return(-1) } + it { is_expected.to run.with_params('-1+1').and_return(-1) } + it { is_expected.to run.with_params('1-1').and_return(1) } + it { is_expected.to run.with_params('1 kaboom').and_return(1024) } + it { is_expected.to run.with_params('kaboom').and_return(0) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/try_get_value_spec.rb b/environments/prod/modules/stdlib/spec/functions/try_get_value_spec.rb new file mode 100644 index 0000000..38c0efd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/try_get_value_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe 'try_get_value' do + + let(:data) do + { + 'a' => { + 'g' => '2', + 'e' => [ + 'f0', + 'f1', + { + 'x' => { + 'y' => 'z' + } + }, + 'f3', + ] + }, + 'b' => true, + 'c' => false, + 'd' => '1', + } + end + + context 'single values' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should be able to return a single value' do + is_expected.to run.with_params('test').and_return('test') + end + + it 'should use the default value if data is a single value and path is present' do + is_expected.to run.with_params('test', 'path', 'default').and_return('default') + end + + it 'should return default if there is no data' do + is_expected.to run.with_params(nil, nil, 'default').and_return('default') + end + + it 'should be able to use data structures as default values' do + is_expected.to run.with_params('test', 'path', data).and_return(data) + end + end + + context 'structure values' do + it 'should be able to extracts a single hash value' do + is_expected.to run.with_params(data, 'd', 'default').and_return('1') + end + + it 'should be able to extract a deeply nested hash value' do + is_expected.to run.with_params(data, 'a/g', 'default').and_return('2') + end + + it 'should return the default value if the path is not found' do + is_expected.to run.with_params(data, 'missing', 'default').and_return('default') + end + + it 'should return the default value if the path is too long' do + is_expected.to run.with_params(data, 'a/g/c/d', 'default').and_return('default') + end + + it 'should support an array index in the path' do + is_expected.to run.with_params(data, 'a/e/1', 'default').and_return('f1') + end + + it 'should return the default value if an array index is not a number' do + is_expected.to run.with_params(data, 'a/b/c', 'default').and_return('default') + end + + it 'should return the default value if and index is out of array length' do + is_expected.to run.with_params(data, 'a/e/5', 'default').and_return('default') + end + + it 'should be able to path though both arrays and hashes' do + is_expected.to run.with_params(data, 'a/e/2/x/y', 'default').and_return('z') + end + + it 'should be able to return "true" value' do + is_expected.to run.with_params(data, 'b', 'default').and_return(true) + is_expected.to run.with_params(data, 'm', true).and_return(true) + end + + it 'should be able to return "false" value' do + is_expected.to run.with_params(data, 'c', 'default').and_return(false) + is_expected.to run.with_params(data, 'm', false).and_return(false) + end + + it 'should return "nil" if value is not found and no default value is provided' do + is_expected.to run.with_params(data, 'a/1').and_return(nil) + end + + it 'should be able to use a custom path separator' do + is_expected.to run.with_params(data, 'a::g', 'default', '::').and_return('2') + is_expected.to run.with_params(data, 'a::c', 'default', '::').and_return('default') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/type3x_spec.rb b/environments/prod/modules/stdlib/spec/functions/type3x_spec.rb new file mode 100644 index 0000000..c3eb1de --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/type3x_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'type3x' do + it "should exist" do + expect(Puppet::Parser::Functions.function("type3x")).to eq("function_type3x") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_type3x([]) }.to( raise_error(Puppet::ParseError)) + end + + it "should return string when given a string" do + result = scope.function_type3x(["aaabbbbcccc"]) + expect(result).to(eq('string')) + end + + it "should return array when given an array" do + result = scope.function_type3x([["aaabbbbcccc","asdf"]]) + expect(result).to(eq('array')) + end + + it "should return hash when given a hash" do + result = scope.function_type3x([{"a"=>1,"b"=>2}]) + expect(result).to(eq('hash')) + end + + it "should return integer when given an integer" do + result = scope.function_type3x(["1"]) + expect(result).to(eq('integer')) + end + + it "should return float when given a float" do + result = scope.function_type3x(["1.34"]) + expect(result).to(eq('float')) + end + + it "should return boolean when given a boolean" do + result = scope.function_type3x([true]) + expect(result).to(eq('boolean')) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/type_of_spec.rb b/environments/prod/modules/stdlib/spec/functions/type_of_spec.rb new file mode 100644 index 0000000..cc9ef78 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/type_of_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +if ENV["FUTURE_PARSER"] == 'yes' + describe 'type_of' do + pending 'teach rspec-puppet to load future-only functions under 3.7.5' do + it { is_expected.not_to eq(nil) } + end + end +end + +if Puppet.version.to_f >= 4.0 + describe 'type_of' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('', '').and_raise_error(ArgumentError) } + + it 'gives the type of a string' do + expect(subject.call({}, 'hello world')).to be_kind_of(Puppet::Pops::Types::PStringType) + end + + it 'gives the type of an integer' do + expect(subject.call({}, 5)).to be_kind_of(Puppet::Pops::Types::PIntegerType) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/type_spec.rb b/environments/prod/modules/stdlib/spec/functions/type_spec.rb new file mode 100755 index 0000000..4288df0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/type_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'type' do + it "should exist" do + expect(Puppet::Parser::Functions.function("type")).to eq("function_type") + end + + it "should give a deprecation warning when called" do + scope.expects(:warning).with("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + scope.function_type(["aoeu"]) + end + + it "should return string when given a string" do + result = scope.function_type(["aaabbbbcccc"]) + expect(result).to(eq('string')) + end + + it "should return array when given an array" do + result = scope.function_type([["aaabbbbcccc","asdf"]]) + expect(result).to(eq('array')) + end + + it "should return hash when given a hash" do + result = scope.function_type([{"a"=>1,"b"=>2}]) + expect(result).to(eq('hash')) + end + + it "should return integer when given an integer" do + result = scope.function_type(["1"]) + expect(result).to(eq('integer')) + end + + it "should return float when given a float" do + result = scope.function_type(["1.34"]) + expect(result).to(eq('float')) + end + + it "should return boolean when given a boolean" do + result = scope.function_type([true]) + expect(result).to(eq('boolean')) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/union_spec.rb b/environments/prod/modules/stdlib/spec/functions/union_spec.rb new file mode 100755 index 0000000..3f36f24 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/union_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'union' do + describe 'argument checking' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + end + + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one'], []).and_return(['one']) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one'], ['two']).and_return(['one', 'two']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two'], ['two', 'three'], ['one', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two'], ['three', 'four'], ['one', 'two', 'three'], ['four']).and_return(['one', 'two', 'three', 'four']) } + it { is_expected.to run.with_params(['ốńə', 'ţשׂợ'], ['ŧĥяếệ', 'ƒởųŗ'], ['ốńə', 'ţשׂợ', 'ŧĥяếệ'], ['ƒởųŗ']).and_return(['ốńə', 'ţשׂợ', 'ŧĥяếệ', 'ƒởųŗ']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return(['1', '2', '3', 1, 2]) end +end diff --git a/environments/prod/modules/stdlib/spec/functions/unique_spec.rb b/environments/prod/modules/stdlib/spec/functions/unique_spec.rb new file mode 100755 index 0000000..7955acb --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/unique_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'unique' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + context 'when called with an array' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['a', 'b', 'a']).and_return(['a', 'b']) } + it { is_expected.to run.with_params(['ã', 'ъ', 'ã']).and_return(['ã', 'ъ']) } + end + + context 'when called with a string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('aaba').and_return('ab') } + it { is_expected.to run.with_params('ããъã').and_return('ãъ') } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/unix2dos_spec.rb b/environments/prod/modules/stdlib/spec/functions/unix2dos_spec.rb new file mode 100644 index 0000000..8537a26 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/unix2dos_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'unix2dos' do + context 'Checking parameter validity' do + it { is_expected.not_to eq(nil) } + it do + is_expected.to run.with_params.and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params('one', 'two').and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + end + end + + context 'Converting from unix to dos format' do + sample_text = "Hello\nWorld\n" + desired_output = "Hello\r\nWorld\r\n" + + it 'should output dos format' do + should run.with_params(sample_text).and_return(desired_output) + end + end + + context 'Converting from dos to dos format' do + sample_text = "Hello\r\nWorld\r\n" + desired_output = "Hello\r\nWorld\r\n" + + it 'should output dos format' do + should run.with_params(sample_text).and_return(desired_output) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/upcase_spec.rb b/environments/prod/modules/stdlib/spec/functions/upcase_spec.rb new file mode 100755 index 0000000..3b7b02d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/upcase_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'upcase' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires an array, hash or object that responds to upcase/) } + it { is_expected.to run.with_params([1]).and_raise_error(Puppet::ParseError, /Requires an array, hash or object that responds to upcase/) } + end + + describe 'normal string handling' do + it { is_expected.to run.with_params("abc").and_return("ABC") } + it { is_expected.to run.with_params("Abc").and_return("ABC") } + it { is_expected.to run.with_params("ABC").and_return("ABC") } + end + + describe 'handling classes derived from String' do + it { is_expected.to run.with_params(AlsoString.new("ABC")).and_return("ABC") } + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["One", "twO"]).and_return(["ONE", "TWO"]) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/uriescape_spec.rb b/environments/prod/modules/stdlib/spec/functions/uriescape_spec.rb new file mode 100755 index 0000000..f05ec08 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/uriescape_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'uriescape' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + describe 'handling normal strings' do + it 'should call ruby\'s URI.escape function' do + URI.expects(:escape).with('uri_string').returns('escaped_uri_string').once + is_expected.to run.with_params('uri_string').and_return('escaped_uri_string') + end + end + + describe 'handling classes derived from String' do + it 'should call ruby\'s URI.escape function' do + uri_string = AlsoString.new('uri_string') + URI.expects(:escape).with(uri_string).returns('escaped_uri_string').once + is_expected.to run.with_params(uri_string).and_return("escaped_uri_string") + end + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["one}", "two"]).and_return(["one%7D", "two"]) } + it { is_expected.to run.with_params(["one}", 1, true, {}, "two"]).and_return(["one%7D", 1, true, {}, "two"]) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_absolute_path_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_absolute_path_spec.rb new file mode 100755 index 0000000..9397da5 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_absolute_path_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe 'validate_absolute_path' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('c:/') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe "valid paths handling" do + %w{ + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + / + /var/tmp + /var/opt/../lib/puppet + }.each do |path| + it { is_expected.to run.with_params(path) } + it { is_expected.to run.with_params(['/tmp', path]) } + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + ].each do |path| + it { is_expected.to run.with_params(path).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params([path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params(['/tmp', path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + end + end + + context 'relative paths' do + %w{ + relative1 + . + .. + ./foo + ../foo + etc/puppetlabs/puppet + opt/puppet/bin + relative\\windows + }.each do |path| + it { is_expected.to run.with_params(path).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params([path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params(['/tmp', path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + end + end + end +end + diff --git a/environments/prod/modules/stdlib/spec/functions/validate_array_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_array_spec.rb new file mode 100755 index 0000000..409b3dc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_array_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'validate_array' do + + describe 'signature validation' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + it { is_expected.not_to eq(nil) } + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params([]) + end + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params([]) } + it { is_expected.to run.with_params(['one']) } + it { is_expected.to run.with_params([], ['two']) } + it { is_expected.to run.with_params(['one'], ['two']) } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], {}).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], 1).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], true).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], 'one').and_raise_error(Puppet::ParseError, /is not an Array/) } + end + end +end + diff --git a/environments/prod/modules/stdlib/spec/functions/validate_augeas_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_augeas_spec.rb new file mode 100755 index 0000000..4236649 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_augeas_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'validate_augeas' do + unless Puppet.features.augeas? + skip "ruby-augeas not installed" + else + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', [], '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'one', 'MSG to User', '4th arg').and_raise_error(NoMethodError) } + end + + describe 'valid inputs' do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns' ], + [ "proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns'], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input) } + end + end + + describe 'valid inputs which fail augeas validation' do + # The intent here is to make sure valid inputs raise exceptions when they + # don't specify an error message to display. This is the behvior in + # 2.2.x and prior. + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns' ], + [ "127.0.1.1\n", 'Hosts.lns' ], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /validate_augeas.*?matched less than it should/) } + end + end + + describe "when specifying nice error messages" do + # The intent here is to make sure the function returns the 4th argument + # in the exception thrown + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns', [], 'Failed to validate passwd content' ], + [ "127.0.1.1\n", 'Hosts.lns', [], 'Wrong hosts content' ], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /#{input[3]}/) } + end + end + + describe "matching additional tests" do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input) } + end + end + + describe "failing additional tests" do + inputs = [ + [ "foobar:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/sh\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /testing path/) } + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_bool_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_bool_spec.rb new file mode 100755 index 0000000..3074d88 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_bool_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'validate_bool' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(true) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'acceptable values' do + it { is_expected.to run.with_params(true) } + it { is_expected.to run.with_params(false) } + it { is_expected.to run.with_params(true, false, false, true) } + end + + describe 'validation failures' do + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params(true, 'one').and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params('one', false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("false").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params(true, "false").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true", false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true", false, false, false, false, false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_cmd_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_cmd_spec.rb new file mode 100755 index 0000000..c6559e8 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_cmd_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'validate_cmd', :unless => Puppet::Util::Platform.windows? do + let(:touch) { File.exists?('/usr/bin/touch') ? '/usr/bin/touch' : '/bin/touch' } + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params([], '', '').and_raise_error(Puppet::ParseError, /content must be a string/) + } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params('', [], '').and_raise_error(Puppet::ParseError, /checkscript must be a string/) + } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params('', '', []).and_raise_error(Puppet::ParseError, /custom error message must be a string/) + } + end + + context 'when validation fails' do + context 'with % placeholder' do + it { is_expected.to run.with_params('', "#{touch} % /no/such/file").and_raise_error(Puppet::ParseError, /Execution of '#{touch} \S+ \/no\/such\/file' returned 1:.*(cannot touch|o such file or)/) } + it { is_expected.to run.with_params('', "#{touch} % /no/such/file", 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + end + context 'without % placeholder' do + it { is_expected.to run.with_params('', "#{touch} /no/such/file").and_raise_error(Puppet::ParseError, /Execution of '#{touch} \/no\/such\/file \S+' returned 1:.*(cannot touch|o such file or)/) } + it { is_expected.to run.with_params('', "#{touch} /no/such/file", 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_email_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_email_address_spec.rb new file mode 100644 index 0000000..7628383 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_email_address_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'validate_email_address' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('bob@gmail.com') } + it { is_expected.to run.with_params('alice+puppetlabs.com@gmail.com') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid email/) } + it { is_expected.to run.with_params('bob@gmail.com', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('bob@gmail.com', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('bob@gmail.com', 'one').and_raise_error(Puppet::ParseError, /is not a valid email/) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_hash_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_hash_spec.rb new file mode 100755 index 0000000..7533abe --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_hash_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'validate_hash' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'check for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params({'key' => 'value'}) + end + end + + describe 'valid inputs' do + it { is_expected.to run.with_params({}) } + it { is_expected.to run.with_params({'key' => 'value'}) } + it { is_expected.to run.with_params({}, {'key' => 'value'}) } + it { is_expected.to run.with_params({'key1' => 'value1'}, {'key2' => 'value2'}) } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, []).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, 1).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, true).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, 'one').and_raise_error(Puppet::ParseError, /is not a Hash/) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_integer_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_integer_spec.rb new file mode 100755 index 0000000..6558d00 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_integer_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe 'validate_integer' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(3) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', 7.0, -7.0, {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x'].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params(invalid, 10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params(invalid, 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params([0, 1, 2, invalid, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + end + + context 'when running on ruby, which munges hashes weirdly', :if => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([0, 1, 2, {0=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params(1, '').and_raise_error(Puppet::ParseError, /to be unset or an Integer/) } + it { is_expected.to run.with_params(1, 2, '').and_raise_error(Puppet::ParseError, /to be unset or an Integer/) } + it { is_expected.to run.with_params(1, 2, 3).and_raise_error(Puppet::ParseError, /second argument to be larger than third argument/) } + end + + context 'with no range constraints' do + it { is_expected.to run.with_params(1) } + it { is_expected.to run.with_params(-1) } + it { is_expected.to run.with_params('1') } + it { is_expected.to run.with_params('-1') } + it { is_expected.to run.with_params([1, 2, 3, 4]) } + it { is_expected.to run.with_params([1, '2', '3', 4]) } + end + + context "with a maximum limit of 10" do + describe 'rejects numbers greater than the limit' do + it { is_expected.to run.with_params(11, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10,100], 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'accepts numbers less or equal to the limit' do + it { is_expected.to run.with_params(10, 10) } + it { is_expected.to run.with_params(1, 10) } + it { is_expected.to run.with_params(-1, 10) } + it { is_expected.to run.with_params('1', 10) } + it { is_expected.to run.with_params('-1', 10) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10) } + end + + context "with a minimum limit of -10" do + describe 'rejects numbers greater than the upper limit' do + it { is_expected.to run.with_params(11, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10,100], 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'rejects numbers smaller than the lower limit' do + it { is_expected.to run.with_params(-11, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-100, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-2**65, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params([-10, 1,2,10,-100], 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + end + + describe 'accepts numbers between and including the limits' do + it { is_expected.to run.with_params(10, 10, -10) } + it { is_expected.to run.with_params(-10, 10, -10) } + it { is_expected.to run.with_params(1, 10, -10) } + it { is_expected.to run.with_params(-1, 10, -10) } + it { is_expected.to run.with_params('1', 10, -10) } + it { is_expected.to run.with_params('-1', 10, -10) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10, -10) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10, -10) } + end + end + end + + it { is_expected.to run.with_params(10, 10, 10) } + + describe 'empty upper limit is interpreted as infinity' do + it { is_expected.to run.with_params(11, '', 10) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_ip_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_ip_address_spec.rb new file mode 100644 index 0000000..0414f5e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_ip_address_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe 'validate_ip_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('0.0.0.0') } + it { is_expected.to run.with_params('8.8.8.8') } + it { is_expected.to run.with_params('127.0.0.1') } + it { is_expected.to run.with_params('10.10.10.10') } + it { is_expected.to run.with_params('194.232.104.150') } + it { is_expected.to run.with_params('244.24.24.24') } + it { is_expected.to run.with_params('255.255.255.255') } + it { is_expected.to run.with_params('1.2.3.4', '5.6.7.8') } + it { is_expected.to run.with_params('3ffe:0505:0002::') } + it { is_expected.to run.with_params('3ffe:0505:0002::', '3ffe:0505:0002::2') } + it { is_expected.to run.with_params('::1/64') } + it { is_expected.to run.with_params('fe80::a00:27ff:fe94:44d6/64') } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1.2.3.4') + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('1.2.3.4') + end + end + + context 'with netmasks' do + it { is_expected.to run.with_params('8.8.8.8/0') } + it { is_expected.to run.with_params('8.8.8.8/16') } + it { is_expected.to run.with_params('8.8.8.8/32') } + it { is_expected.to run.with_params('8.8.8.8/255.255.0.0') } + end + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0.256').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('1.2.3.4', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', 1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', 'one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('::1', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 'one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb new file mode 100755 index 0000000..6e4ca05 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'validate_ipv4_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first) + end + end + + SharedData::IPV4_PATTERNS.each do |value| + it { is_expected.to run.with_params(value) } + end + + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_raise_error(Puppet::ParseError, /is not a valid IPv4/) } + end + + describe 'invalid inputs' do + [ {}, [], 1, true ].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first, invalid).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb new file mode 100755 index 0000000..137db36 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe 'validate_ipv6_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('3ffe:0505:0002::') + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('3ffe:0505:0002::') + end + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('3ffe:0505:0002::') } + it { is_expected.to run.with_params('3ffe:0505:0002::', '3ffe:0505:0002::2') } + it { is_expected.to run.with_params('::1/64') } + it { is_expected.to run.with_params('fe80::a00:27ff:fe94:44d6/64') } + it { is_expected.to run.with_params('fe80:0000:0000:0000:0204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80:0:0:0:204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80::204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80:0:0:0:0204:61ff:254.157.241.86') } + it { is_expected.to run.with_params('::1') } + it { is_expected.to run.with_params('fe80::') } + it { is_expected.to run.with_params('2001::') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0.256').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:2.3.4').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:257.1.2.3').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:12345678901234567890.1.26').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('affe:beef').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::1', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 'one').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + context 'unless running on ruby 1.8.7', :if => RUBY_VERSION != '1.8.7' do + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 1).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_legacy_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_legacy_spec.rb new file mode 100644 index 0000000..7b48f12 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_legacy_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.4.0') >= 0 + describe 'validate_legacy' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + + describe 'when passing the type assertion and passing the previous validation' do + before do + scope.expects(:function_validate_foo).with([5]).once + Puppet.expects(:notice).never + end + it 'passes without notice' do + is_expected.to run.with_params('Integer', 'validate_foo', 5) + end + end + + describe 'when passing the type assertion and failing the previous validation' do + before do + scope.expects(:function_validate_foo).with([5]).raises(Puppet::ParseError, 'foo').once + Puppet.expects(:notice).with(includes('Accepting previously invalid value for target type')) + end + it 'passes with a notice about newly accepted value' do + is_expected.to run.with_params('Integer', 'validate_foo', 5) + end + end + + describe 'when failing the type assertion and passing the previous validation' do + before do + scope.expects(:function_validate_foo).with(['5']).once + subject.func.expects(:call_function).with('deprecation', 'validate_legacy', includes('Integer')).once + end + it 'passes with a deprecation message' do + is_expected.to run.with_params('Integer', 'validate_foo', '5') + end + end + + describe 'when failing the type assertion and failing the previous validation' do + before do + scope.expects(:function_validate_foo).with(['5']).raises(Puppet::ParseError, 'foo').once + subject.func.expects(:call_function).with('fail', includes('Integer')).once + end + it 'fails with a helpful message' do + is_expected.to run.with_params('Integer', 'validate_foo', '5') + end + end + + describe 'when passing in undef' do + before do + scope.expects(:function_validate_foo).with([:undef]).once + Puppet.expects(:notice).never + end + it 'works' do + is_expected.to run.with_params('Optional[Integer]', 'validate_foo', :undef) + end + end + + describe 'when passing in multiple arguments' do + before do + scope.expects(:function_validate_foo).with([:undef, 1, 'foo']).once + Puppet.expects(:notice).never + end + it 'passes with a deprecation message' do + is_expected.to run.with_params('Optional[Integer]', 'validate_foo', :undef, 1, 'foo') + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_numeric_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_numeric_spec.rb new file mode 100755 index 0000000..4c0e24d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_numeric_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe 'validate_numeric' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(3) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x'].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + it { is_expected.to run.with_params(invalid, 10.0).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + it { is_expected.to run.with_params(invalid, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + end + + context 'when running on ruby, which munges hashes weirdly', :if => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([0, 1, 2, {0=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params(1, '').and_raise_error(Puppet::ParseError, /to be unset or a Numeric/) } + it { is_expected.to run.with_params(1, 2, '').and_raise_error(Puppet::ParseError, /to be unset or a Numeric/) } + it { is_expected.to run.with_params(1, 2, 3).and_raise_error(Puppet::ParseError, /second argument to be larger than third argument/) } + end + + context 'with no range constraints' do + it { is_expected.to run.with_params(1) } + it { is_expected.to run.with_params(-1) } + it { is_expected.to run.with_params('1') } + it { is_expected.to run.with_params('-1') } + it { is_expected.to run.with_params([1, 2, 3, 4]) } + it { is_expected.to run.with_params([1, '2', '3', 4]) } + end + + context "with a maximum limit of 10.0" do + describe 'rejects numbers greater than the limit' do + it { is_expected.to run.with_params(11, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10.0,100], 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'accepts numbers less or equal to the limit' do + it { is_expected.to run.with_params(10.0, 10.0) } + it { is_expected.to run.with_params(1, 10.0) } + it { is_expected.to run.with_params(-1, 10.0) } + it { is_expected.to run.with_params('1', 10.0) } + it { is_expected.to run.with_params('-1', 10.0) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10.0) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10.0) } + end + + context "with a minimum limit of -10.0" do + describe 'rejects numbers greater than the upper limit' do + it { is_expected.to run.with_params(11, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10.0,100], 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'rejects numbers smaller than the lower limit' do + it { is_expected.to run.with_params(-11, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-100, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-2**65, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params([-10.0, 1,2,10.0,-100], 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + end + + describe 'accepts numbers between and including the limits' do + it { is_expected.to run.with_params(10.0, 10.0, -10.0) } + it { is_expected.to run.with_params(-10.0, 10.0, -10.0) } + it { is_expected.to run.with_params(1, 10.0, -10.0) } + it { is_expected.to run.with_params(-1, 10.0, -10.0) } + it { is_expected.to run.with_params('1', 10.0, -10.0) } + it { is_expected.to run.with_params('-1', 10.0, -10.0) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10.0, -10.0) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10.0, -10.0) } + end + end + end + + it { is_expected.to run.with_params(10.0, 10.0, 10.0) } + + describe 'empty upper limit is interpreted as infinity' do + it { is_expected.to run.with_params(11, '', 10.0) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_re_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_re_spec.rb new file mode 100755 index 0000000..3531182 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_re_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'validate_re' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('', '') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params('', '') } + it { is_expected.to run.with_params('', ['']) } + it { is_expected.to run.with_params('', [''], 'custom error') } + it { is_expected.to run.with_params('one', '^one') } + it { is_expected.to run.with_params('one', [ '^one', '^two' ]) } + it { is_expected.to run.with_params('one', [ '^one', '^two' ], 'custom error') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params('', []).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', 'two').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', ['two']).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', ['two'], 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + it { is_expected.to run.with_params('notone', '^one').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('notone', [ '^one', '^two' ]).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('notone', [ '^one', '^two' ], 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + + describe 'non-string inputs' do + [ + 1, # Fixnum + 3.14, # Float + nil, # NilClass + true, # TrueClass + false, # FalseClass + ["10"], # Array + :key, # Symbol + {:key=>"val"}, # Hash + ].each do |input| + it { is_expected.to run.with_params(input, '.*').and_raise_error(Puppet::ParseError, /needs to be a String/) } + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_slength_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_slength_spec.rb new file mode 100755 index 0000000..e4162de --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_slength_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'validate_slength' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1234567890', 10) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', 2, 3, 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /second argument to be a positive Numeric/) } + it { is_expected.to run.with_params('', -1).and_raise_error(Puppet::ParseError, /second argument to be a positive Numeric/) } + it { is_expected.to run.with_params('', 1, '').and_raise_error(Puppet::ParseError, /third argument to be unset or a positive Numeric/) } + it { is_expected.to run.with_params('', 1, -1).and_raise_error(Puppet::ParseError, /third argument to be unset or a positive Numeric/) } + it { is_expected.to run.with_params('', 1, 2).and_raise_error(Puppet::ParseError, /argument to be equal to or larger than third argument/) } + end + + context "with a maximum length of 10" do + describe 'rejects strings longer than the limit' do + it { is_expected.to run.with_params('1234567890a', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params('1234567890abcdef', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params([ 'one', '1234567890abcdef' ], 10).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'accepts strings shorter or equal to the limit' do + it { is_expected.to run.with_params('1234567890', 10) } + it { is_expected.to run.with_params('12345', 10) } + it { is_expected.to run.with_params([ 'one', 'two' ], 10) } + end + + context "with a minimum length of 5" do + describe 'rejects strings longer than the upper limit' do + it { is_expected.to run.with_params('1234567890a', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params('1234567890abcdef', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'rejects numbers shorter than the lower limit' do + it { is_expected.to run.with_params('one', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params(['12345678', 'two'], 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'accepts strings of length between and including the limits' do + it { is_expected.to run.with_params('12345', 10, 5) } + it { is_expected.to run.with_params('123456', 10, 5) } + it { is_expected.to run.with_params('1234567', 10, 5) } + it { is_expected.to run.with_params('12345678', 10, 5) } + it { is_expected.to run.with_params('123456789', 10, 5) } + it { is_expected.to run.with_params('1234567890', 10, 5) } + it { is_expected.to run.with_params(['1233456', '12345678'], 10, 5) } + end + end + end + + describe 'corner cases' do + it { pending('this should work'); is_expected.to run.with_params('', 0, 0) } + it { is_expected.to run.with_params('1234567890', 10, 10) } + end + + describe 'empty upper limit is interpreted as infinity' do + it { pending('not implemented'); is_expected.to run.with_params('1234567890ab', '', 10) } + it { pending('not implemented'); is_expected.to run.with_params('12345678', '', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_string_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_string_spec.rb new file mode 100755 index 0000000..0907ede --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_string_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'validate_string' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('', '') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params('') } + it { is_expected.to run.with_params(nil) } + it { is_expected.to run.with_params('one') } + it { is_expected.to run.with_params('one', 'two') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb b/environments/prod/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb new file mode 100755 index 0000000..eb63310 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' + +describe 'validate_x509_rsa_key_pair' do + + let(:valid_cert) do + < 'value' }).and_return(['value']) } + it 'should return the array of values' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2', 'duplicate_value_key' => 'value2' }]) + expect(result).to match_array(['value1', 'value2', 'value2']) + end + + it 'should run with UTF8 and double byte characters' do + result = subject.call([{ 'かぎ' => '使用', 'ҝĕұ' => '√ẩŀứệ', 'ҝĕұďŭрļǐçằťè' => '√ẩŀứệ' }]) + expect(result).to match_array(['使用', '√ẩŀứệ', '√ẩŀứệ']) + end +end diff --git a/environments/prod/modules/stdlib/spec/functions/zip_spec.rb b/environments/prod/modules/stdlib/spec/functions/zip_spec.rb new file mode 100755 index 0000000..e1ae8ee --- /dev/null +++ b/environments/prod/modules/stdlib/spec/functions/zip_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'zip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the third.") + is_expected.to run.with_params([], [], true, []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6]).and_return([[1,4], [2,5], [3,6]]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6], false).and_return([[1,4], [2,5], [3,6]]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6], true).and_return([1, 4, 2, 5, 3, 6]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['ầ', 'ь', 'ć'], ['đ', 'ề', 'ƒ']).and_return([['ầ','đ'], ['ь','ề'], ['ć', 'ƒ']]) } + it { is_expected.to run.with_params(['ペ', '含', '値'], ['ッ', '文', 'イ']).and_return([['ペ','ッ'], ['含','文'], ['値', 'イ']]) } + end +end + diff --git a/environments/prod/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb b/environments/prod/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb new file mode 100755 index 0000000..505e240 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb @@ -0,0 +1,9 @@ +#! /usr/bin/env ruby -S rspec +require 'rspec' + +class Object + # This is necessary because the RAL has a 'should' + # method. + alias :must :should + alias :must_not :should_not +end diff --git a/environments/prod/modules/stdlib/spec/monkey_patches/publicize_methods.rb b/environments/prod/modules/stdlib/spec/monkey_patches/publicize_methods.rb new file mode 100755 index 0000000..3ae59f9 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/monkey_patches/publicize_methods.rb @@ -0,0 +1,11 @@ +#! /usr/bin/env ruby -S rspec +# Some monkey-patching to allow us to test private methods. +class Class + def publicize_methods(*methods) + saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods + + self.class_eval { public(*saved_private_instance_methods) } + yield + self.class_eval { private(*saved_private_instance_methods) } + end +end diff --git a/environments/prod/modules/stdlib/spec/spec_helper.rb b/environments/prod/modules/stdlib/spec/spec_helper.rb new file mode 100755 index 0000000..22d5d68 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/spec_helper.rb @@ -0,0 +1,8 @@ +#This file is generated by ModuleSync, do not edit. +require 'puppetlabs_spec_helper/module_spec_helper' + +# put local configuration and setup into spec_helper_local +begin + require 'spec_helper_local' +rescue LoadError +end diff --git a/environments/prod/modules/stdlib/spec/spec_helper_acceptance.rb b/environments/prod/modules/stdlib/spec/spec_helper_acceptance.rb new file mode 100755 index 0000000..4d85e7d --- /dev/null +++ b/environments/prod/modules/stdlib/spec/spec_helper_acceptance.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'puppet' +require 'beaker-rspec' +require 'beaker/puppet_install_helper' +require 'beaker/module_install_helper' + +run_puppet_install_helper +install_ca_certs unless ENV['PUPPET_INSTALL_TYPE'] =~ /pe/i +install_module_on(hosts) +install_module_dependencies_on(hosts) + +RSpec.configure do |c| + # Readable test descriptions + c.formatter = :documentation + + # Configure all nodes in nodeset + c.before :suite do + end +end + +def get_puppet_version + (on default, puppet('--version')).output.chomp +end + +RSpec.shared_context "with faked facts" do + let(:facts_d) do + puppet_version = get_puppet_version + if fact('osfamily') =~ /windows/i + if fact('kernelmajversion').to_f < 6.0 + 'C:/Documents and Settings/All Users/Application Data/PuppetLabs/facter/facts.d' + else + 'C:/ProgramData/PuppetLabs/facter/facts.d' + end + elsif Puppet::Util::Package.versioncmp(puppet_version, '4.0.0') < 0 and fact('is_pe', '--puppet') == "true" + '/etc/puppetlabs/facter/facts.d' + else + '/etc/facter/facts.d' + end + end + + before :each do + #No need to create on windows, PE creates by default + if fact('osfamily') !~ /windows/i + shell("mkdir -p '#{facts_d}'") + end + end + + after :each do + shell("rm -f '#{facts_d}/fqdn.txt'", :acceptable_exit_codes => [0,1]) + end + + def fake_fact(name, value) + shell("echo #{name}=#{value} > '#{facts_d}/#{name}.txt'") + end +end diff --git a/environments/prod/modules/stdlib/spec/spec_helper_local.rb b/environments/prod/modules/stdlib/spec/spec_helper_local.rb new file mode 100644 index 0000000..616490c --- /dev/null +++ b/environments/prod/modules/stdlib/spec/spec_helper_local.rb @@ -0,0 +1,31 @@ +# automatically load any shared examples or contexts +Dir["./spec/support/**/*.rb"].sort.each { |f| require f } + +# hack to enable all the expect syntax (like allow_any_instance_of) in rspec-puppet examples +RSpec::Mocks::Syntax.enable_expect(RSpec::Puppet::ManifestMatchers) + +RSpec.configure do |config| + # supply tests with a possibility to test for the future parser + config.add_setting :puppet_future + config.puppet_future = Puppet.version.to_f >= 4.0 + + config.before :each do + # Ensure that we don't accidentally cache facts and environment between + # test cases. This requires each example group to explicitly load the + # facts being exercised with something like + # Facter.collection.loader.load(:ipaddress) + Facter.clear + Facter.clear_messages + + RSpec::Mocks.setup + end + + config.after :each do + RSpec::Mocks.verify + RSpec::Mocks.teardown + end +end + +# Helper class to test handling of arguments which are derived from string +class AlsoString < String +end diff --git a/environments/prod/modules/stdlib/spec/support/shared_data.rb b/environments/prod/modules/stdlib/spec/support/shared_data.rb new file mode 100644 index 0000000..ea9b7a0 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/support/shared_data.rb @@ -0,0 +1,38 @@ +module SharedData + IPV4_PATTERNS = [ + '0.0.0.0', + '1.2.3.4', + '10.10.10.10', + '127.0.0.1', + '192.88.99.0', + '194.232.104.150', + '224.0.0.0', + '244.24.24.24', + '255.255.255.255', + '8.8.8.8', + '8.8.8.8/0', + '8.8.8.8/16', + '8.8.8.8/255.255.0.0', + '8.8.8.8/32', + ] + IPV4_NEGATIVE_PATTERNS = [ + '', + '0000', + '0.0.0.0.', + '0.0.0.0./0.0.0.0.', + '0.0.0.0./1', + '0.0.0.0.0', + '0.0.0.0/0.0.0.0.', + '0.0.0.256', + '0.0.0', + '1.2.3.4.5', + '1.2.3', + '10.010.10.10', + '2001:0db8:85a3:0000:0000:8a2e:0370:73342001:0db8:85a3:0000:0000:8a2e:0370:7334', + '4.4.4', + '77', + '9999.9999.9999.9999', + 'affe::beef', + 'nope', + ] +end diff --git a/environments/prod/modules/stdlib/spec/unit/ensure_resources_spec.rb b/environments/prod/modules/stdlib/spec/unit/ensure_resources_spec.rb new file mode 100644 index 0000000..aea723e --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/ensure_resources_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'test::ensure_resources', type: :class do + let(:params) {{ resource_type: 'user', title_hash: title_param, attributes_hash: {'ensure' => 'present'} }} + + describe 'given a title hash of multiple resources' do + + let(:title_param) { {'dan' => { 'gid' => 'mygroup', 'uid' => '600' }, 'alex' => { 'gid' => 'mygroup', 'uid' => '700'}} } + + it { is_expected.to compile } + it { is_expected.to contain_user('dan').with({ 'gid' => 'mygroup', 'uid' => '600', 'ensure' => 'present'}) } + it { is_expected.to contain_user('alex').with({ 'gid' => 'mygroup', 'uid' => '700', 'ensure' => 'present'}) } + end + + describe 'given a title hash of a single resource' do + + let(:title_param) { {'dan' => { 'gid' => 'mygroup', 'uid' => '600' }} } + + it { is_expected.to compile } + it { is_expected.to contain_user('dan').with({ 'gid' => 'mygroup', 'uid' => '600', 'ensure' => 'present'}) } + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb new file mode 100755 index 0000000..0afadb2 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb @@ -0,0 +1,32 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/facter_dot_d' + +describe Facter::Util::DotD do + + context 'returns a simple fact' do + before :each do + Facter.stubs(:version).returns('1.6.1') + subject.stubs(:entries).returns(['/etc/facter/facts.d/fake_fact.txt']) + File.stubs(:readlines).with('/etc/facter/facts.d/fake_fact.txt').returns(['fake_fact=fake fact']) + subject.create + end + + it 'should return successfully' do + expect(Facter.fact(:fake_fact).value).to eq('fake fact') + end + end + + context 'returns a fact with equals signs' do + before :each do + Facter.stubs(:version).returns('1.6.1') + subject.stubs(:entries).returns(['/etc/facter/facts.d/foo.txt']) + File.stubs(:readlines).with('/etc/facter/facts.d/foo.txt').returns(['foo=1+1=2']) + subject.create + end + + it 'should return successfully' do + expect(Facter.fact(:foo).value).to eq('1+1=2') + end + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/package_provider_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/package_provider_spec.rb new file mode 100644 index 0000000..3954faf --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/package_provider_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'puppet/type' +require 'puppet/type/package' + +describe 'package_provider', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + ['4.2.2', '3.7.1 (Puppet Enterprise 3.2.1)'].each do |puppetversion| + describe "on puppet ''#{puppetversion}''" do + before :each do + Facter.stubs(:value).returns puppetversion + end + + context "darwin" do + it "should return pkgdmg" do + provider = Puppet::Type.type(:package).provider(:pkgdmg) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('pkgdmg') + end + end + + context "centos 7" do + it "should return yum" do + provider = Puppet::Type.type(:package).provider(:yum) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('yum') + end + end + + context "ubuntu" do + it "should return apt" do + provider = Puppet::Type.type(:package).provider(:apt) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('apt') + end + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/pe_version_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/pe_version_spec.rb new file mode 100755 index 0000000..c11a1cd --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/pe_version_spec.rb @@ -0,0 +1,88 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "PE Version specs" do + before :each do + # Explicitly load the pe_version.rb file which contains generated facts + # that cannot be automatically loaded. Puppet 2.x implements + # Facter.collection.load while Facter 1.x markes Facter.collection.load as + # a private method. + if Facter.collection.respond_to? :load + Facter.collection.load(:pe_version) + else + Facter.collection.loader.load(:pe_version) + end + end + + context "When puppetversion is nil" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns(nil) + end + + it "pe_version is nil" do + expect(Facter.fact(:puppetversion).value).to be_nil + expect(Facter.fact(:pe_version).value).to be_nil + end + end + + context "If PE is installed" do + %w{ 2.6.1 2.10.300 }.each do |version| + puppetversion = "2.7.19 (Puppet Enterprise #{version})" + context "puppetversion => #{puppetversion}" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns(puppetversion) + end + + (major,minor,patch) = version.split(".") + + it "Should return true" do + expect(Facter.fact(:is_pe).value).to eq(true) + end + + it "Should have a version of #{version}" do + expect(Facter.fact(:pe_version).value).to eq(version) + end + + it "Should have a major version of #{major}" do + expect(Facter.fact(:pe_major_version).value).to eq(major) + end + + it "Should have a minor version of #{minor}" do + expect(Facter.fact(:pe_minor_version).value).to eq(minor) + end + + it "Should have a patch version of #{patch}" do + expect(Facter.fact(:pe_patch_version).value).to eq(patch) + end + end + end + end + + context "When PE is not installed" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns("2.7.19") + end + + it "is_pe is false" do + expect(Facter.fact(:is_pe).value).to eq(false) + end + + it "pe_version is nil" do + expect(Facter.fact(:pe_version).value).to be_nil + end + + it "pe_major_version is nil" do + expect(Facter.fact(:pe_major_version).value).to be_nil + end + + it "pe_minor_version is nil" do + expect(Facter.fact(:pe_minor_version).value).to be_nil + end + + it "Should have a patch version" do + expect(Facter.fact(:pe_patch_version).value).to be_nil + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/root_home_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/root_home_spec.rb new file mode 100755 index 0000000..a5c2846 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/root_home_spec.rb @@ -0,0 +1,65 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/root_home' + +describe Facter::Util::RootHome do + context "solaris" do + let(:root_ent) { "root:x:0:0:Super-User:/:/sbin/sh" } + let(:expected_root_home) { "/" } + + it "should return /" do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent) + expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home) + end + end + context "linux" do + let(:root_ent) { "root:x:0:0:root:/root:/bin/bash" } + let(:expected_root_home) { "/root" } + + it "should return /root" do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent) + expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home) + end + end + context "windows" do + before :each do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(nil) + end + it "should be nil on windows" do + expect(Facter::Util::RootHome.get_root_home).to be_nil + end + end +end + +describe 'root_home', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + context "macosx" do + before do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:osfamily).stubs(:value).returns("Darwin") + end + let(:expected_root_home) { "/var/root" } + sample_dscacheutil = File.read(fixtures('dscacheutil','root')) + + it "should return /var/root" do + Facter::Util::Resolution.stubs(:exec).with("dscacheutil -q user -a name root").returns(sample_dscacheutil) + expect(Facter.fact(:root_home).value).to eq(expected_root_home) + end + end + + context "aix" do + before do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter.fact(:osfamily).stubs(:value).returns("AIX") + end + let(:expected_root_home) { "/root" } + sample_lsuser = File.read(fixtures('lsuser','root')) + + it "should return /root" do + Facter::Util::Resolution.stubs(:exec).with("lsuser -c -a home root").returns(sample_lsuser) + expect(Facter.fact(:root_home).value).to eq(expected_root_home) + end + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/service_provider_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/service_provider_spec.rb new file mode 100644 index 0000000..ad8a5fc --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/service_provider_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'puppet/type' +require 'puppet/type/service' + +describe 'service_provider', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + context "macosx" do + it "should return launchd" do + provider = Puppet::Type.type(:service).provider(:launchd) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('launchd') + end + end + + context "systemd" do + it "should return systemd" do + provider = Puppet::Type.type(:service).provider(:systemd) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('systemd') + end + end + + context "redhat" do + it "should return redhat" do + provider = Puppet::Type.type(:service).provider(:redhat) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('redhat') + end + end + +end diff --git a/environments/prod/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb b/environments/prod/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb new file mode 100755 index 0000000..c278b79 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/util/puppet_settings' + +describe Facter::Util::PuppetSettings do + + describe "#with_puppet" do + context "Without Puppet loaded" do + before(:each) do + Module.expects(:const_get).with("Puppet").raises(NameError) + end + + it 'should be nil' do + expect(subject.with_puppet { Puppet[:vardir] }).to be_nil + end + it 'should not yield to the block' do + Puppet.expects(:[]).never + expect(subject.with_puppet { Puppet[:vardir] }).to be_nil + end + end + context "With Puppet loaded" do + module Puppet; end + let(:vardir) { "/var/lib/puppet" } + + before :each do + Puppet.expects(:[]).with(:vardir).returns vardir + end + + it 'should yield to the block' do + subject.with_puppet { Puppet[:vardir] } + end + it 'should return the nodes vardir' do + expect(subject.with_puppet { Puppet[:vardir] }).to eq vardir + end + end + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb b/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb new file mode 100644 index 0000000..b162127 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb @@ -0,0 +1,69 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the enclose_ipv6 function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + expect(Puppet::Parser::Functions.function("enclose_ipv6")).to eq("function_enclose_ipv6") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_enclose_ipv6([]) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError if there is more than 1 arguments" do + expect { scope.function_enclose_ipv6(['argument1','argument2']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given garbage" do + expect { scope.function_enclose_ipv6(['garbage']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given something else than a string or an array" do + expect { scope.function_enclose_ipv6([['1' => '127.0.0.1']]) }.to( raise_error(Puppet::ParseError) ) + end + + it "should not raise a ParseError when given a single ip string" do + expect { scope.function_enclose_ipv6(['127.0.0.1']) }.to_not raise_error + end + + it "should not raise a ParseError when given * as ip string" do + expect { scope.function_enclose_ipv6(['*']) }.to_not raise_error + end + + it "should not raise a ParseError when given an array of ip strings" do + expect { scope.function_enclose_ipv6([['127.0.0.1','fe80::1']]) }.to_not raise_error + end + + it "should not raise a ParseError when given differently notations of ip addresses" do + expect { scope.function_enclose_ipv6([['127.0.0.1','fe80::1','[fe80::1]']]) }.to_not raise_error + end + + it "should raise a ParseError when given a wrong ipv4 address" do + expect { scope.function_enclose_ipv6(['127..0.0.1']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given a ipv4 address with square brackets" do + expect { scope.function_enclose_ipv6(['[127.0.0.1]']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given a wrong ipv6 address" do + expect { scope.function_enclose_ipv6(['fe80:::1']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should embrace ipv6 adresses within an array of ip addresses" do + result = scope.function_enclose_ipv6([['127.0.0.1','fe80::1','[fe80::2]']]) + expect(result).to(eq(['127.0.0.1','[fe80::1]','[fe80::2]'])) + end + + it "should embrace a single ipv6 adresse" do + result = scope.function_enclose_ipv6(['fe80::1']) + expect(result).to(eq(['[fe80::1]'])) + end + + it "should not embrace a single ipv4 adresse" do + result = scope.function_enclose_ipv6(['127.0.0.1']) + expect(result).to(eq(['127.0.0.1'])) + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb b/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb new file mode 100644 index 0000000..8931208 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe :is_absolute_path do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + let(:function_args) do + [] + end + + let(:function) do + scope.function_is_absolute_path(function_args) + end + + + describe 'validate arity' do + let(:function_args) do + [1,2] + end + it "should raise a ParseError if there is more than 1 arguments" do + lambda { function }.should( raise_error(ArgumentError)) + end + + end + + it "should exist" do + Puppet::Parser::Functions.function(subject).should == "function_#{subject}" + end + + # help enforce good function defination + it 'should contain arity' do + + end + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { function }.should( raise_error(ArgumentError)) + end + + + describe 'should retrun true' do + let(:return_value) do + true + end + + describe 'windows' do + let(:function_args) do + ['c:\temp\test.txt'] + end + it 'should return data' do + function.should eq(return_value) + end + end + + describe 'non-windows' do + let(:function_args) do + ['/temp/test.txt'] + end + + it 'should return data' do + function.should eq(return_value) + end + end + end + + describe 'should return false' do + let(:return_value) do + false + end + describe 'windows' do + let(:function_args) do + ['..\temp\test.txt'] + end + it 'should return data' do + function.should eq(return_value) + end + end + + describe 'non-windows' do + let(:function_args) do + ['../var/lib/puppet'] + end + it 'should return data' do + function.should eq(return_value) + end + end + end +end \ No newline at end of file diff --git a/environments/prod/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb b/environments/prod/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb new file mode 100755 index 0000000..1f41f62 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb @@ -0,0 +1,447 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'tempfile' +provider_class = Puppet::Type.type(:file_line).provider(:ruby) +describe provider_class do + context "when adding" do + let :tmpfile do + tmp = Tempfile.new('tmp') + path = tmp.path + tmp.close! + path + end + let :resource do + Puppet::Type::File_line.new( + {:name => 'foo', :path => tmpfile, :line => 'foo'} + ) + end + let :provider do + provider_class.new(resource) + end + + it 'should detect if the line exists in the file' do + File.open(tmpfile, 'w') do |fh| + fh.write('foo') + end + expect(provider.exists?).to be_truthy + end + it 'should detect if the line does not exist in the file' do + File.open(tmpfile, 'w') do |fh| + fh.write('foo1') + end + expect(provider.exists?).to be_nil + end + it 'should append to an existing file when creating' do + provider.create + expect(File.read(tmpfile).chomp).to eq('foo') + end + end + context 'when using replace' do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :replace => false, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should not replace the matching line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo3") + end + expect(@provider.exists?).to be_truthy + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo=blah\nfoo2\nfoo3") + end + + it 'should append the line if no matches are found' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo2\nfoo = bar") + end + + it 'should raise an error with invalid values' do + expect { + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :replace => 'asgadga', + } + ) + }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + end + end + context "when matching" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + } + ) + @provider = provider_class.new(@resource) + end + + describe 'using match' do + it 'should raise an error if more than one line matches, and should not have modified the file' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + expect(@provider.exists?).to be_nil + expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) + expect(File.read(@tmpfile)).to eql("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + + it 'should replace all lines that matches' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :multiple => true, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2\nfoo = bar") + end + + it 'should raise an error with invalid values' do + expect { + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :multiple => 'asgadga', + } + ) + }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + end + + it 'should replace a line that matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + end + it 'should add a new line if no lines match' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo = bar\n") + end + it 'should do nothing if the exact line already exists' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = bar\nfoo2") + end + expect(@provider.exists?).to be_truthy + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + end + end + + describe 'using after' do + let :resource do + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => '^foo1', + } + ) + end + + let :provider do + provider_class.new(resource) + end + context 'match and after set' do + shared_context 'resource_create' do + let(:match) { '^foo2$' } + let(:after) { '^foo1$' } + let(:resource) { + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => after, + :match => match, + } + ) + } + end + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2\nfoo = baz") + end + end + describe 'inserts at match' do + include_context 'resource_create' + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo = baz") + } + end + describe 'inserts a new line after when no match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } + end + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo2\nfoo = baz") + } + end + describe 'append to end of file if no match for both after and match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } + let(:after) { '^stillneverafter' } + end + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\nfoo2\nfoo = baz\ninserted = line") + } + end + end + context 'with one line matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") + end + end + + it 'inserts the specified line after the line matching the "after" expression' do + provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz") + end + end + + context 'with multiple lines matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz") + end + end + + it 'errors out stating "One or no line must match the pattern"' do + expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/) + end + + it 'adds the line after all lines matching the after expression' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => '^foo1$', + :multiple => true, + } + ) + @provider = provider_class.new(@resource) + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo1\ninserted = line\nfoo = baz") + end + end + + context 'with no lines matching the after expression' do + let :content do + "foo3\nfoo = blah\nfoo2\nfoo = baz\n" + end + + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write(content) + end + end + + it 'appends the specified line to the file' do + provider.create + expect(File.read(@tmpfile)).to eq(content << resource[:line] << "\n") + end + end + end + end + + context "when removing" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo', + :ensure => 'absent', + } + ) + @provider = provider_class.new(@resource) + end + it 'should remove the line if it exists' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + end + + it 'should remove the line without touching the last new line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\n") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + + it 'should remove any occurence of the line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + end + + context "when removing with a match" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :match_for_absence => true, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should find a line to match' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + expect(@provider.exists?).to be_truthy + end + + it 'should remove one line if it matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + end + + it 'should raise an error if more than one line matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + expect { @provider.destroy }.to raise_error(Puppet::Error, /More than one line/) + end + + it 'should remove multiple lines if :multiple is true' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :multiple => true, + :match_for_absence => true, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + + it 'should ignore the match if match_for_absence is not specified' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + end + + it 'should ignore the match if match_for_absence is false' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :match_for_absence => false, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + end + + end + +end diff --git a/environments/prod/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb b/environments/prod/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb new file mode 100755 index 0000000..c738a27 --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +anchor = Puppet::Type.type(:anchor).new(:name => "ntp::begin") + +describe anchor do + it "should stringify normally" do + expect(anchor.to_s).to eq("Anchor[ntp::begin]") + end +end diff --git a/environments/prod/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb b/environments/prod/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb new file mode 100755 index 0000000..150149b --- /dev/null +++ b/environments/prod/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb @@ -0,0 +1,113 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'tempfile' +describe Puppet::Type.type(:file_line) do + let :tmp_path do + if Puppet::Util::Platform.windows? + 'C:\tmp\path' + else + '/tmp/path' + end + end + let :my_path do + if Puppet::Util::Platform.windows? + 'C:\my\path' + else + '/my/path' + end + end + let :file_line do + Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'line', :path => tmp_path) + end + it 'should accept a line and path' do + file_line[:line] = 'my_line' + expect(file_line[:line]).to eq('my_line') + file_line[:path] = my_path + expect(file_line[:path]).to eq(my_path) + end + it 'should accept a match regex' do + file_line[:match] = '^foo.*$' + expect(file_line[:match]).to eq('^foo.*$') + end + it 'should accept a match regex that does not match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => my_path, + :line => 'foo=bar', + :match => '^bar=blah$' + )}.not_to raise_error + end + it 'should accept a match regex that does match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => my_path, + :line => 'foo=bar', + :match => '^\s*foo=.*$' + )}.not_to raise_error + end + it 'should accept utf8 characters' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'ƒồỗ', + :path => my_path, + :line => 'ƒồỗ=ьåя', + :match => '^ьåя=βļάħ$' + )}.not_to raise_error + end + it 'should accept double byte characters' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'フーバー', + :path => my_path, + :line => 'この=それ', + :match => '^この=ああ$' + )}.not_to raise_error + end + it 'should accept posix filenames' do + file_line[:path] = tmp_path + expect(file_line[:path]).to eq(tmp_path) + end + it 'should not accept unqualified path' do + expect { file_line[:path] = 'file' }.to raise_error(Puppet::Error, /File paths must be fully qualified/) + end + it 'should require that a line is specified' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path) }.to raise_error(Puppet::Error, /line is a required attribute/) + end + it 'should not require that a line is specified when matching for absence' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path, :ensure => :absent, :match_for_absence => :true, :match => 'match') }.not_to raise_error + end + it 'should require that a file is specified' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'path') }.to raise_error(Puppet::Error, /path is a required attribute/) + end + it 'should default to ensure => present' do + expect(file_line[:ensure]).to eq :present + end + it 'should default to replace => true' do + expect(file_line[:replace]).to eq :true + end + it 'should default to encoding => UTF-8' do + expect(file_line[:encoding]).to eq 'UTF-8' + end + it 'should accept encoding => iso-8859-1' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path, :ensure => :present, :encoding => 'iso-8859-1', :line => 'bar') }.not_to raise_error + end + it "should autorequire the file it manages" do + catalog = Puppet::Resource::Catalog.new + file = Puppet::Type.type(:file).new(:name => tmp_path) + catalog.add_resource file + catalog.add_resource file_line + + relationship = file_line.autorequire.find do |rel| + (rel.source.to_s == "File[#{tmp_path}]") and (rel.target.to_s == file_line.to_s) + end + expect(relationship).to be_a Puppet::Relationship + end + + it "should not autorequire the file it manages if it is not managed" do + catalog = Puppet::Resource::Catalog.new + catalog.add_resource file_line + expect(file_line.autorequire).to be_empty + end +end diff --git a/environments/prod/modules/stdlib/types/absolutepath.pp b/environments/prod/modules/stdlib/types/absolutepath.pp new file mode 100644 index 0000000..70ec916 --- /dev/null +++ b/environments/prod/modules/stdlib/types/absolutepath.pp @@ -0,0 +1,2 @@ +# A strict absolutepath type +type Stdlib::Absolutepath = Variant[Stdlib::Windowspath, Stdlib::Unixpath] diff --git a/environments/prod/modules/stdlib/types/compat/absolute_path.pp b/environments/prod/modules/stdlib/types/compat/absolute_path.pp new file mode 100644 index 0000000..d11784e --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/absolute_path.pp @@ -0,0 +1,7 @@ +# Emulate the is_absolute_path and validate_absolute_path functions +# +# The first pattern is originally from is_absolute_path, which had it from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path? +# slash = '[\\\\/]' +# name = '[^\\\\/]+' +# %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, +type Stdlib::Compat::Absolute_path = Variant[Pattern[/^(([a-zA-Z]:[\\\/])|([\\\/][\\\/][^\\\/]+[\\\/][^\\\/]+)|([\\\/][\\\/]\?[\\\/][^\\\/]+))/], Pattern[/^\//]] diff --git a/environments/prod/modules/stdlib/types/compat/array.pp b/environments/prod/modules/stdlib/types/compat/array.pp new file mode 100644 index 0000000..ba65dc4 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/array.pp @@ -0,0 +1,2 @@ +# Emulate the is_array and validate_array functions +type Stdlib::Compat::Array = Array[Any] diff --git a/environments/prod/modules/stdlib/types/compat/bool.pp b/environments/prod/modules/stdlib/types/compat/bool.pp new file mode 100644 index 0000000..5d8e27e --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/bool.pp @@ -0,0 +1,2 @@ +# Emulate the is_bool and validate_bool functions +type Stdlib::Compat::Bool = Boolean diff --git a/environments/prod/modules/stdlib/types/compat/float.pp b/environments/prod/modules/stdlib/types/compat/float.pp new file mode 100644 index 0000000..7f98bd2 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/float.pp @@ -0,0 +1,19 @@ +# Emulate the is_float function +# The regex is what's currently used in is_float +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_float($value,) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Float $value) { +# validate_float($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Float = Variant[Float, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)(?:[eE]-?\d+)?$/]] diff --git a/environments/prod/modules/stdlib/types/compat/hash.pp b/environments/prod/modules/stdlib/types/compat/hash.pp new file mode 100644 index 0000000..e84a10b --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/hash.pp @@ -0,0 +1,2 @@ +# Emulate the is_hash and validate_hash functions +type Stdlib::Compat::Hash = Hash[Any, Any] diff --git a/environments/prod/modules/stdlib/types/compat/integer.pp b/environments/prod/modules/stdlib/types/compat/integer.pp new file mode 100644 index 0000000..e5cadb6 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/integer.pp @@ -0,0 +1,23 @@ +# Emulate the is_integer and validate_integer functions +# The regex is what's currently used in is_integer +# validate_numeric also allows range checking, which cannot be mapped to the string parsing inside the function. +# For full backwards compatibility, you will need to keep the validate_numeric call around to catch everything. +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_integer($value, 10, 0) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Integer $value) { +# validate_numeric($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# > Note that you need to use Variant[Integer[0, 10], Float[0, 10]] if you want to match both integers and floating point numbers. +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Integer = Variant[Integer, Pattern[/^-?(?:(?:[1-9]\d*)|0)$/], Array[Variant[Integer, Pattern[/^-?(?:(?:[1-9]\d*)|0)$/]]]] diff --git a/environments/prod/modules/stdlib/types/compat/ip_address.pp b/environments/prod/modules/stdlib/types/compat/ip_address.pp new file mode 100644 index 0000000..bf4c4b4 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/ip_address.pp @@ -0,0 +1 @@ +type Stdlib::Compat::Ip_address = Variant[Stdlib::Compat::Ipv4, Stdlib::Compat::Ipv6] diff --git a/environments/prod/modules/stdlib/types/compat/ipv4.pp b/environments/prod/modules/stdlib/types/compat/ipv4.pp new file mode 100644 index 0000000..a0ba0d6 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/ipv4.pp @@ -0,0 +1,2 @@ +# Emulate the validate_ipv4_address and is_ipv4_address functions +type Stdlib::Compat::Ipv4 = Pattern[/^((([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))[.]){3}([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d)))(\/((([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))[.]){3}([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))|[0-9]+))?$/] diff --git a/environments/prod/modules/stdlib/types/compat/ipv6.pp b/environments/prod/modules/stdlib/types/compat/ipv6.pp new file mode 100644 index 0000000..8b82f1a --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/ipv6.pp @@ -0,0 +1 @@ +type Stdlib::Compat::Ipv6 = Pattern[/\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/] diff --git a/environments/prod/modules/stdlib/types/compat/numeric.pp b/environments/prod/modules/stdlib/types/compat/numeric.pp new file mode 100644 index 0000000..5bfc3d3 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/numeric.pp @@ -0,0 +1,23 @@ +# Emulate the is_numeric and validate_numeric functions +# The regex is what's currently used in is_numeric +# validate_numeric also allows range checking, which cannot be mapped to the string parsing inside the function. +# For full backwards compatibility, you will need to keep the validate_numeric call around to catch everything. +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_numeric($value, 10, 0) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Numeric $value) { +# validate_numeric($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# > Note that you need to use Variant[Integer[0, 10], Float[0, 10]] if you want to match both integers and floating point numbers. +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Numeric = Variant[Numeric, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$/], Array[Variant[Numeric, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$/]]]] diff --git a/environments/prod/modules/stdlib/types/compat/re.pp b/environments/prod/modules/stdlib/types/compat/re.pp new file mode 100644 index 0000000..e4b5f30 --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/re.pp @@ -0,0 +1,3 @@ +# Emulate the validate_re function +# validate_re(value, re) translates to Pattern[re], which is not directly mappable as a type alias, but can be specified as Pattern[re]. +# Therefore this needs to be translated directly. diff --git a/environments/prod/modules/stdlib/types/compat/string.pp b/environments/prod/modules/stdlib/types/compat/string.pp new file mode 100644 index 0000000..b06255d --- /dev/null +++ b/environments/prod/modules/stdlib/types/compat/string.pp @@ -0,0 +1,2 @@ +# Emulate the is_string and validate_string functions +type Stdlib::Compat::String = Optional[String] diff --git a/environments/prod/modules/stdlib/types/httpsurl.pp b/environments/prod/modules/stdlib/types/httpsurl.pp new file mode 100644 index 0000000..36fd30f --- /dev/null +++ b/environments/prod/modules/stdlib/types/httpsurl.pp @@ -0,0 +1 @@ +type Stdlib::HTTPSUrl = Pattern[/^https:\/\//] diff --git a/environments/prod/modules/stdlib/types/httpurl.pp b/environments/prod/modules/stdlib/types/httpurl.pp new file mode 100644 index 0000000..0d93a95 --- /dev/null +++ b/environments/prod/modules/stdlib/types/httpurl.pp @@ -0,0 +1 @@ +type Stdlib::HTTPUrl = Pattern[/^https?:\/\//] diff --git a/environments/prod/modules/stdlib/types/unixpath.pp b/environments/prod/modules/stdlib/types/unixpath.pp new file mode 100644 index 0000000..ec3bf7d --- /dev/null +++ b/environments/prod/modules/stdlib/types/unixpath.pp @@ -0,0 +1,2 @@ +# this regex rejects any path component that is a / or a NUL +type Stdlib::Unixpath = Pattern[/^\/([^\/\0]+\/*)+$/] diff --git a/environments/prod/modules/stdlib/types/windowspath.pp b/environments/prod/modules/stdlib/types/windowspath.pp new file mode 100644 index 0000000..bc1ee9c --- /dev/null +++ b/environments/prod/modules/stdlib/types/windowspath.pp @@ -0,0 +1 @@ +type Stdlib::Windowspath = Pattern[/^(([a-zA-Z]:[\\\/])|([\\\/][\\\/][^\\\/]+[\\\/][^\\\/]+)|([\\\/][\\\/]\?[\\\/][^\\\/]+))/] diff --git a/environments/production/environment.conf b/environments/production/environment.conf new file mode 100644 index 0000000..c39193f --- /dev/null +++ b/environments/production/environment.conf @@ -0,0 +1,18 @@ +# Each environment can have an environment.conf file. Its settings will only +# affect its own environment. See docs for more info: +# https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html + +# Any unspecified settings use default values; some of those defaults are based +# on puppet.conf settings. + +# If these settings include relative file paths, they'll be resolved relative to +# this environment's directory. + +# Allowed settings and default values: + +# modulepath = ./modules:$basemodulepath +# manifest = (default_manifest from puppet.conf, which defaults to ./manifests) +# config_version = (no script; Puppet will use the time the catalog was compiled) +# environment_timeout = (environment_timeout from puppet.conf, which defaults to 0) + # Note: unless you have a specific reason, we recommend only setting + # environment_timeout in puppet.conf. diff --git a/environments/production/manifests/site.pp b/environments/production/manifests/site.pp new file mode 100644 index 0000000..c541fdd --- /dev/null +++ b/environments/production/manifests/site.pp @@ -0,0 +1,21 @@ +node node1.kuzniatsou.local { + include '::mysql::server' + + +# Ensure that the database 'test_mdb' is created, the user 'test_user' with assigned privileges exists +mysql::db { 'test_mdb': + user => 'test_user', + password => 'password', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +} + +node master1.kuzniatsou.local { + package { 'htop': + ensure => 'installed', + } + package { 'vim': + ensure => 'installed', + } +} diff --git a/environments/production/modules/mysql/CHANGELOG.md b/environments/production/modules/mysql/CHANGELOG.md new file mode 100644 index 0000000..5f07f6e --- /dev/null +++ b/environments/production/modules/mysql/CHANGELOG.md @@ -0,0 +1,770 @@ +## Supported Release 3.10.0 +### Summary +This release includes new features for setting TLS options on a mysql user, a new parameter to allow specifying tool to import sql files, as well as various bugfixes. + +#### Features +- (MODULES-3879) Adds `import_cat_cmd` parameter to specify the command to read sql files +- Adds support for setting `tls_options` in `mysql_user` + +#### Bugfixes +- (MODULES-3557) Adds Ubuntu 16.04 package names for language bindings +- (MODULES-3907) Adds MySQL/Percona 5.7 initialize on fresh deploy + +## Supported Release 3.9.0 +### Summary +This release adds Percona 5.7 support and compatibility with Ubuntu 16.04, in addition to various bugfixes. + +#### Features +- (MODULES-3441) Adds the `mysqld_version` fact +- (MODULES-3513) Adds a new backup dump parameter `maxallowedpacket` +- Adds new parameter `xtrabackup_package_name` to `mysql::backup::xtrabackup` class +- Adds ability to revoke GRANT privilege + +#### Bugfixes +- Fixes a bug where `mysql_user` fails if facter cannot retrieve fqdn. +- Fix global parameter usage in backup script +- Adds support for `puppet-staging` version `2.0.0` +- (MODULES-3601) Moves binary logging configuration to take place after package install +- (MODULES-3711) Add limit to mysql server ID generated value +- (MODULES-3698) Fixes defaults for SLES12 +- Updates user name length restrictions for MySQL version 5.7.8 and above. +- Fixes a bug where error log is not writable by owner + +## Supported Release 3.8.0 +###Summary +This release adds Percona 5.7 support and compatibility with Ubuntu 16.04, in addition to various bugfixes. + +#### Features +- Adds support for Percona 5.7 +- Adds support for Ubuntu 16.04 (Xenial) + +#### Known Limitations +- The mysqlbackup.sh script will not work on MySQL 5.7.0 and up. + +#### Bugfixes +- Use mysql_install_db only with uniq defaults-extra-file +- Updates mysqlbackup.sh to ensure backup directory exist +- Loosen MariaDB recognition to fix it on Debian 8 +- Allow mysql::backup::mysqldump to access root_group in tests +- Fixed problem with ignoring parameters from global configs +- Fixes ordering issue that initialized mysqld before config is set +- (MODULES-1256) Fix parameters on OpenSUSE 12 +- Fixes install errors on Debian-based OS by configuring the base of includedir +- Configure the configfile location for mariadb +- Default mysqld_type return value should be 'mysql' if another type is not detected +- Make sure that bzip2 is installed before setting up the cron tab job using mysqlbackup.sh +- Fixes path issue on FreeBSD +- Check that /var/lib/mysql actually contains files +- Removes mysql regex when checking type +- (MODULES-2111) Add the system database to user related actions +- Updates default group for logfiles on Debian-based OS to 'adm' +- Fixes an issue with Amazon linux major release 4 installation +- Fixes 'mysql_install_db' script support on Gentoo +- Removes erroneous anchors to mysql::client from mysql::db +- Adds path to be able to find MySQL 5.5 installation on CentOS + +## Supported Release 3.7.0 +###Summary + +A large release with several new features. Also includes a considerable amount of bugfixes, many around compatibility and improvements to current functionality. + +#### Features + +- Now uses mariadb in OpenSuSE >= 13.1. +- Switch to rspec-puppet-facts. +- Additional function to check if table exists before grant. +- Add ability to input password hash directly. +- Now checking major release instead of specific release. +- Debian 8 support. + +#### Bugfixes + +- Minor doc update. +- Fixes improper use of function `warn` in backup manifest of server. +- Fixes to Compatibility with PE 3.3. +- Fixes `when not managing config file` in `mysql_server_spec`. +- Improved user validation and munging. +- Fixes fetching the mysql_user password for MySQL >=5.7.6. +- Fixes unique server_id within my.cnf, the issue were the entire mac address was not being read in to generate the id. +- Corrects the daemon_dev_package_name for mariadb on redhat. +- Fix version compare to properly suppress show_diff for root password. +- Fixes to ensure compatibility with future parser. +- Solaris removed from PE in metadata as its not supported. +- Use MYSQL_PWD to avoid mysqldump warnings. +- Use temp cnf file instead of env variable which creates acceptance test failures. +- No longer hash passwords that are already hashed. +- Fix Gemfile to work with ruby 1.8.7. +- Fixed MySQL 5.7.6++ compatibility. +- Fixing error when disabling service management and the service does not exist. +- Ubuntu vivid should use systemd not upstart. +- Fixed new mysql_datadir provider on CentOS for MySQl 5.7.6 compatibility. +- Ensure if service restart to wait till mysql is up. +- Move all dependencies to not have them in case of service unmanaged. +- Re-Added the ability to set a empty string as option parameter. +- Fixes edge-case with dropping pre-existing users with grants. +- Fix logic for choosing rspec version. +- Refactored main acceptance suite. +- Skip idempotency tests on test cells that do have PUP-5016 unfixed. +- Fix tmpdir to be shared across examples. +- Update to current msync configs [006831f]. +- Fix mysql_grant with MySQL ANSI_QUOTES mode. +- Generate .my.cnf for all sections. + +## Supported Release 3.6.2 +###Summary + +Small release for support of newer PE versions. This increments the version of PE in the metadata.json file. + +## 2015-09-22 - Supported Release 3.6.1 +### Summary +This is a security and bugfix release that fixes incorrect username truncation in the munge for the mysql_user type, incorrect function used in `mysql::server::backup` and fixes compatibility issues with PE 3.3.x. + +#### Bugfixes +- Loosen the regex in mysql_user munging so the username is not unintentionally truncated. +- Use `warning()` not `warn()` +- Metadata had inadvertantly dropped 3.3.x support +- Some 3.3.x compatibility issues in `mysqltuner` were corrected + +## 2015-08-10 - Supported Release 3.6.0 +### Summary +This release adds the ability to use mysql::db and `mysql_*` types against unmanaged or external mysql instances. + +#### Features +- Add ability to use mysql::db WITHOUT mysql::server (ie, externally) +- Add prescript attribute to mysql::server::backup for xtrabackup +- Add postscript ability to xtrabackup provider. + +#### Bugfixes +- Fix default root passwords blocking puppet on mysql 5.8 +- Fix service dependency when package_manage is false +- Fix selinux permissions on my.cnf + +##2015-07-23 - Supported Release 3.5.0 +###Summary +A small release to add explicit support to newer Puppet versions and accumulated patches. + +####Features/Improvements +- Start running tests against puppet 4 +- Support longer usernames on newer MariaDB versions +- Add parameters for Solaris 11 and 12 + +####Bugfixes +- Fix references to the mysql-server package +- mysql_server_id doesn't throw and error on machines without macaddress + +##2015-05-19 - Supported Release 3.4.0 +###Summary +This release includes the addition of extra facts, OpenBSD compatibility, and a number of other features, improvements and bug fixes. + +####Features/Improvements +- Added server_id fact which includes mac address for better uniqueness +- Added OpenBSD compatibility, only for 'OpenBSD -current' (due to the recent switch to mariadb) +- Added a $mysql_group parameter, and use that instead of the $root_group parameter to define the group membership of the mysql error log file. +- Updated tests for rspec-puppet 2 and future parser +- Further acceptance testing improvements +- MODULES-1928 - allow log-error to be undef +- Split package installation and database install +- README wording improvements +- Added options for including/excluding triggers and routines +- Made the 'TRIGGER' privilege of mysqldump backups depend on whether or not we are actually backing up triggers +- Cleaned up the privilege assignment in the mysqldump backup script +- Add a fact for capturing the mysql version installed + +####Bugfixes +- mysql backup: fix regression in mysql_user call +- Set service_ensure to undef, in the case of an unmanaged service +- README Typos fixed +- Bugfix on Xtrabackup crons +- Fixed a permission problem that was preventing triggers from being backed up +- MODULES-1981: Revoke and grant difference of old and new privileges +- Fix an issue were we assume triggers work +- Change default for mysql::server::backup to ignore_triggers = false + +####Deprecations +mysql::server::old_root_password property + +##2015-03-03 - Supported Release 3.3.0 +###Summary +This release includes major README updates, the addition of backup providers, and a fix for managing the log-bin directory. + +####Features +- Add package_manage parameters to `mysql::server` and `mysql::client` (MODULES-1143) +- README improvements +- Add `mysqldump`, `mysqlbackup`, and `xtrabackup` backup providers. + +####Bugfixes +- log-error overrides were not being properly used (MODULES-1804) +- check for full path for log-bin to stop puppet from managing file '.' + +##2015-02-09 - Supported Release 3.2.0 +###Summary +This release includes several new features and bugfixes, including support for various plugins, making the output from mysql_password more consistent when input is empty and improved username validation. + +####Features +- Add type and provider to manage plugins +- Add support for authentication plugins +- Add support for mysql_install_db on freebsd +- Add `create_root_user` and `create_root_my_cnf` parameters to `mysql::server` + +####Bugfixes +- Remove dependency on stdlib >= 4.1.0 (MODULES-1759) +- Make grant autorequire user +- Remove invalid parameter 'provider' from mysql_user instance (MODULES-1731) +- Return empty string for empty input in mysql_password +- Fix `mysql::account_security` when fqdn==localhost +- Update username validation (MODULES-1520) +- Future parser fix in params.pp +- Fix package name for debian 8 +- Don't start the service until the server package is installed and the config file is in place +- Test fixes +- Lint fixes + +##2014-12-16 - Supported Release 3.1.0 +###Summary + +This release includes several new features, including SLES12 support, and a number of bug fixes. + +####Notes + +`mysql::server::mysqltuner` has been refactored to fetch the mysqltuner script from github by default. If you are running on a non-network-connected system, you will need to download that file and have it available to your node at a path specified by the `source` parameter to the `mysqltuner` class. + +####Features +- Add support for install_options for all package resources (MODULES-1484) +- Add log-bin directory creation +- Allow mysql::db to import multiple files (MODULES-1338) +- SLES12 support +- Improved identifier quoting detections +- Reworked `mysql::server::mysqltuner` so that we are no longer packaging the script as it is licensed under the GPL. + +####Bugfixes +- Fix regression in username validation +- Proper containment for mysql::client in mysql::db +- Support quoted usernames of length 15 and 16 chars + +##2014-11-11 - Supported Release 3.0.0 +###Summary + +Added several new features including MariaDB support and future parser + +####Backwards-incompatible Changes +* Remove the deprecated `database`, `database_user`, and `database_grant` resources. The correct resources to use are `mysql`, `mysql_user`, and `mysql_grant` respectively. + +####Features +* Add MariaDB Support +* The mysqltuner perl script has been updated to 1.3.0 based on work at http://github.com/major/MySQLTuner-perl +* Add future parse support, fixed issues with undef to empty string +* Pass the backup credentials to 'SHOW DATABASES' +* Ability to specify the Includedir for `mysql::server` +* `mysql::db` now has an import\_timeout feature that defaults to 300 +* The `mysql` class has been removed +* `mysql::server` now takes an `override_options` hash that will affect the installation +* Ability to install both dev and client dev + +####BugFix +* `mysql::server::backup` now passes `ensure` param to the nested `mysql_grant` +* `mysql::server::service` now properly requires the presence of the `log_error` file +* `mysql::config` now occurs before `mysql::server::install_db` correctly + +##2014-07-15 - Supported Release 2.3.1 +###Summary + +This release merely updates metadata.json so the module can be uninstalled and +upgraded via the puppet module command. + +##2014-05-14 - Supported Release 2.3.0 + +This release primarily adds support for RHEL7 and Ubuntu 14.04 but it +also adds a couple of new parameters to allow for further customization, +as well as ensuring backups can backup stored procedures properly. + +####Features +Added `execpath` to allow a custom executable path for non-standard mysql installations. +Added `dbname` to mysql::db and use ensure_resource to create the resource. +Added support for RHEL7 and Fedora Rawhide. +Added support for Ubuntu 14.04. +Create a warning for if you disable SSL. +Ensure the error logfile is owned by MySQL. +Disable ssl on FreeBSD. +Add PROCESS privilege for backups. + +####Bugfixes + +####Known Bugs +* No known bugs + +##2014-03-04 - Supported Release 2.2.3 +###Summary + +This is a supported release. This release removes a testing symlink that can +cause trouble on systems where /var is on a seperate filesystem from the +modulepath. + +####Features +####Bugfixes +####Known Bugs +* No known bugs + +##2014-03-04 - Supported Release 2.2.2 +###Summary +This is a supported release. Mostly comprised of enhanced testing, plus a +bugfix for Suse. + +####Bugfixes +- PHP bindings on Suse +- Test fixes + +####Known Bugs +* No known bugs + +##2014-02-19 - Version 2.2.1 + +###Summary + +Minor release that repairs mysql_database{} so that it sees the correct +collation settings (it was only checking the global mysql ones, not the +actual database and constantly setting it over and over since January 22nd). + +Also fixes a bunch of tests on various platforms. + + +##2014-02-13 - Version 2.2.0 + +###Summary + +####Features +- Add `backupdirmode`, `backupdirowner`, `backupdirgroup` to + mysql::server::backup to allow customizing the mysqlbackupdir. +- Support multiple options of the same name, allowing you to + do 'replicate-do-db' => ['base1', 'base2', 'base3'] in order to get three + lines of replicate-do-db = base1, replicate-do-db = base2 etc. + +####Bugfixes +- Fix `restart` so it actually stops mysql restarting if set to false. +- DRY out the defaults_file functionality in the providers. +- mysql_grant fixed to work with root@localhost/@. +- mysql_grant fixed for WITH MAX_QUERIES_PER_HOUR +- mysql_grant fixed so revoking all privileges accounts for GRANT OPTION +- mysql_grant fixed to remove duplicate privileges. +- mysql_grant fixed to handle PROCEDURES when removing privileges. +- mysql_database won't try to create existing databases, breaking replication. +- bind_address renamed bind-address in 'mysqld' options. +- key_buffer renamed to key_buffer_size. +- log_error renamed to log-error. +- pid_file renamed to pid-file. +- Ensure mysql::server:root_password runs before mysql::server::backup +- Fix options_override -> override_options in the README. +- Extensively rewrite the README to be accurate and awesome. +- Move to requiring stdlib 3.2.0, shipped in PE3.0 +- Add many new tests. + + +##2013-11-13 - Version 2.1.0 + +###Summary + +The most important changes in 2.1.0 are improvements to the my.cnf creation, +as well as providers. Setting options to = true strips them to be just the +key name itself, which is required for some options. + +The provider updates fix a number of bugs, from lowercase privileges to +deprecation warnings. + +Last, the new hiera integration functionality should make it easier to +externalize all your grants, users, and, databases. Another great set of +community submissions helped to make this release. + +####Features +- Some options can not take a argument. Gets rid of the '= true' when an +option is set to true. +- Easier hiera integration: Add hash parameters to mysql::server to allow +specifying grants, users, and databases. + +####Bugfixes +- Fix an issue with lowercase privileges in mysql_grant{} causing them to be reapplied needlessly. +- Changed defaults-file to defaults-extra-file in providers. +- Ensure /root/.my.cnf is 0600 and root owned. +- database_user deprecation warning was incorrect. +- Add anchor pattern for client.pp +- Documentation improvements. +- Various test fixes. + + +##2013-10-21 - Version 2.0.1 + +###Summary + +This is a bugfix release to handle an issue where unsorted mysql_grant{} +privileges could cause Puppet to incorrectly reapply the permissions on +each run. + +####Bugfixes +- Mysql_grant now sorts privileges in the type and provider for comparison. +- Comment and test tweak for PE3.1. + + +##2013-10-14 - Version 2.0.0 + +###Summary + +(Previously detailed in the changelog for 2.0.0-rc1) + +This module has been completely refactored and works significantly different. +The changes are broad and touch almost every piece of the module. + +See the README.md for full details of all changes and syntax. +Please remain on 1.0.0 if you don't have time to fully test this in dev. + +* mysql::server, mysql::client, and mysql::bindings are the primary interface +classes. +* mysql::server takes an `override_options` parameter to set my.cnf options, +with the hash format: { 'section' => { 'thing' => 'value' }} +* mysql attempts backwards compatibility by forwarding all parameters to +mysql::server. + + +##2013-10-09 - Version 2.0.0-rc5 + +###Summary + +Hopefully the final rc! Further fixes to mysql_grant (stripping out the +cleverness so we match a much wider range of input.) + +####Bugfixes +- Make mysql_grant accept '.*'@'.*' in terms of input for user@host. + + +##2013-10-09 - Version 2.0.0-rc4 + +###Summary + +Bugfixes to mysql_grant and mysql_user form the bulk of this rc, as well as +ensuring that values in the override_options hash that contain a value of '' +are created as just "key" in the conf rather than "key =" or "key = false". + +####Bugfixes +- Improve mysql_grant to work with IPv6 addresses (both long and short). +- Ensure @host users work as well as user@host users. +- Updated my.cnf template to support items with no values. + + +##2013-10-07 - Version 2.0.0-rc3 + +###Summary +Fix mysql::server::monitor's use of mysql_user{}. + +####Bugfixes +- Fix myql::server::monitor's use of mysql_user{} to grant the proper +permissions. Add specs as well. (Thanks to treydock!) + + +##2013-10-03 - Version 2.0.0-rc2 + +###Summary +Bugfixes + +####Bugfixes +- Fix a duplicate parameter in mysql::server + + +##2013-10-03 - Version 2.0.0-rc1 + +###Summary + +This module has been completely refactored and works significantly different. +The changes are broad and touch almost every piece of the module. + +See the README.md for full details of all changes and syntax. +Please remain on 1.0.0 if you don't have time to fully test this in dev. + +* mysql::server, mysql::client, and mysql::bindings are the primary interface +classes. +* mysql::server takes an `override_options` parameter to set my.cnf options, +with the hash format: { 'section' => { 'thing' => 'value' }} +* mysql attempts backwards compatibility by forwarding all parameters to +mysql::server. + +--- +##2013-09-23 - Version 1.0.0 + +###Summary + +This release introduces a number of new type/providers, to eventually +replace the database_ ones. The module has been converted to call the +new providers rather than the previous ones as they have a number of +fixes, additional options, and work with puppet resource. + +This 1.0.0 release precedes a large refactoring that will be released +almost immediately after as 2.0.0. + +####Features +- Added mysql_grant, mysql_database, and mysql_user. +- Add `mysql::bindings` class and refactor all other bindings to be contained underneath mysql::bindings:: namespace. +- Added support to back up specified databases only with 'mysqlbackup' parameter. +- Add option to mysql::backup to set the backup script to perform a mysqldump on each database to its own file + +####Bugfixes +- Update my.cnf.pass.erb to allow custom socket support +- Add environment variable for .my.cnf in mysql::db. +- Add HOME environment variable for .my.cnf to mysqladmin command when +(re)setting root password + +--- +##2013-07-15 - Version 0.9.0 +####Features +- Add `mysql::backup::backuprotate` parameter +- Add `mysql::backup::delete_before_dump` parameter +- Add `max_user_connections` attribute to `database_user` type + +####Bugfixes +- Add client package dependency for `mysql::db` +- Remove duplicate `expire_logs_days` and `max_binlog_size` settings +- Make root's `.my.cnf` file path dynamic +- Update pidfile path for Suse variants +- Fixes for lint + +##2013-07-05 - Version 0.8.1 +####Bugfixes + - Fix a typo in the Fedora 19 support. + +##2013-07-01 - Version 0.8.0 +####Features + - mysql::perl class to install perl-DBD-mysql. + - minor improvements to the providers to improve reliability + - Install the MariaDB packages on Fedora 19 instead of MySQL. + - Add new `mysql` class parameters: + - `max_connections`: The maximum number of allowed connections. + - `manage_config_file`: Opt out of puppetized control of my.cnf. + - `ft_min_word_len`: Fine tune the full text search. + - `ft_max_word_len`: Fine tune the full text search. + - Add new `mysql` class performance tuning parameters: + - `key_buffer` + - `thread_stack` + - `thread_cache_size` + - `myisam-recover` + - `query_cache_limit` + - `query_cache_size` + - `max_connections` + - `tmp_table_size` + - `table_open_cache` + - `long_query_time` + - Add new `mysql` class replication parameters: + - `server_id` + - `sql_log_bin` + - `log_bin` + - `max_binlog_size` + - `binlog_do_db` + - `expire_logs_days` + - `log_bin_trust_function_creators` + - `replicate_ignore_table` + - `replicate_wild_do_table` + - `replicate_wild_ignore_table` + - `expire_logs_days` + - `max_binlog_size` + +####Bugfixes + - No longer restart MySQL when /root/.my.cnf changes. + - Ensure mysql::config runs before any mysql::db defines. + +##2013-06-26 - Version 0.7.1 +####Bugfixes +- Single-quote password for special characters +- Update travis testing for puppet 3.2.x and missing Bundler gems + +##2013-06-25 - Version 0.7.0 +This is a maintenance release for community bugfixes and exposing +configuration variables. + +* Add new `mysql` class parameters: + - `basedir`: The base directory mysql uses + - `bind_address`: The IP mysql binds to + - `client_package_name`: The name of the mysql client package + - `config_file`: The location of the server config file + - `config_template`: The template to use to generate my.cnf + - `datadir`: The directory MySQL's datafiles are stored + - `default_engine`: The default engine to use for tables + - `etc_root_password`: Whether or not to add the mysql root password to + /etc/my.cnf + - `java_package_name`: The name of the java package containing the java + connector + - `log_error`: Where to log errors + - `manage_service`: Boolean dictating if mysql::server should manage the + service + - `max_allowed_packet`: Maximum network packet size mysqld will accept + - `old_root_password`: Previous root user password + - `php_package_name`: The name of the phpmysql package to install + - `pidfile`: The location mysql will expect the pidfile to be + - `port`: The port mysql listens on + - `purge_conf_dir`: Value fed to recurse and purge parameters of the + /etc/mysql/conf.d resource + - `python_package_name`: The name of the python mysql package to install + - `restart`: Whether to restart mysqld + - `root_group`: Use specified group for root-owned files + - `root_password`: The root MySQL password to use + - `ruby_package_name`: The name of the ruby mysql package to install + - `ruby_package_provider`: The installation suite to use when installing the + ruby package + - `server_package_name`: The name of the server package to install + - `service_name`: The name of the service to start + - `service_provider`: The name of the service provider + - `socket`: The location of the MySQL server socket file + - `ssl_ca`: The location of the SSL CA Cert + - `ssl_cert`: The location of the SSL Certificate to use + - `ssl_key`: The SSL key to use + - `ssl`: Whether or not to enable ssl + - `tmpdir`: The directory MySQL's tmpfiles are stored +* Deprecate `mysql::package_name` parameter in favor of +`mysql::client_package_name` +* Fix local variable template deprecation +* Fix dependency ordering in `mysql::db` +* Fix ANSI quoting in queries +* Fix travis support (but still messy) +* Fix typos + +##2013-01-11 - Version 0.6.1 +* Fix providers when /root/.my.cnf is absent + +##2013-01-09 - Version 0.6.0 +* Add `mysql::server::config` define for specific config directives +* Add `mysql::php` class for php support +* Add `backupcompress` parameter to `mysql::backup` +* Add `restart` parameter to `mysql::config` +* Add `purge_conf_dir` parameter to `mysql::config` +* Add `manage_service` parameter to `mysql::server` +* Add syslog logging support via the `log_error` parameter +* Add initial SuSE support +* Fix remove non-localhost root user when fqdn != hostname +* Fix dependency in `mysql::server::monitor` +* Fix .my.cnf path for root user and root password +* Fix ipv6 support for users +* Fix / update various spec tests +* Fix typos +* Fix lint warnings + +##2012-08-23 - Version 0.5.0 +* Add puppetlabs/stdlib as requirement +* Add validation for mysql privs in provider +* Add `pidfile` parameter to mysql::config +* Add `ensure` parameter to mysql::db +* Add Amazon linux support +* Change `bind_address` parameter to be optional in my.cnf template +* Fix quoting root passwords + +##2012-07-24 - Version 0.4.0 +* Fix various bugs regarding database names +* FreeBSD support +* Allow specifying the storage engine +* Add a backup class +* Add a security class to purge default accounts + +##2012-05-03 - Version 0.3.0 +* 14218 Query the database for available privileges +* Add mysql::java class for java connector installation +* Use correct error log location on different distros +* Fix set_mysql_rootpw to properly depend on my.cnf + +##2012-04-11 - Version 0.2.0 + +##2012-03-19 - William Van Hevelingen +* (#13203) Add ssl support (f7e0ea5) + +##2012-03-18 - Nan Liu +* Travis ci before script needs success exit code. (0ea463b) + +##2012-03-18 - Nan Liu +* Fix Puppet 2.6 compilation issues. (9ebbbc4) + +##2012-03-16 - Nan Liu +* Add travis.ci for testing multiple puppet versions. (33c72ef) + +##2012-03-15 - William Van Hevelingen +* (#13163) Datadir should be configurable (f353fc6) + +##2012-03-16 - Nan Liu +* Document create_resources dependency. (558a59c) + +##2012-03-16 - Nan Liu +* Fix spec test issues related to error message. (eff79b5) + +##2012-03-16 - Nan Liu +* Fix mysql service on Ubuntu. (72da2c5) + +##2012-03-16 - Dan Bode +* Add more spec test coverage (55e399d) + +##2012-03-16 - Nan Liu +* (#11963) Fix spec test due to path changes. (1700349) + +##2012-03-07 - François Charlier +* Add a test to check path for 'mysqld-restart' (b14c7d1) + +##2012-03-07 - François Charlier +* Fix path for 'mysqld-restart' (1a9ae6b) + +##2012-03-15 - Dan Bode +* Add rspec-puppet tests for mysql::config (907331a) + +##2012-03-15 - Dan Bode +* Moved class dependency between sever and config to server (da62ad6) + +##2012-03-14 - Dan Bode +* Notify mysql restart from set_mysql_rootpw exec (0832a2c) + +##2012-03-15 - Nan Liu +* Add documentation related to osfamily fact. (8265d28) + +##2012-03-14 - Dan Bode +* Mention osfamily value in failure message (e472d3b) + +##2012-03-14 - Dan Bode +* Fix bug when querying for all database users (015490c) + +##2012-02-09 - Nan Liu +* Major refactor of mysql module. (b1f90fd) + +##2012-01-11 - Justin Ellison +* Ruby and Python's MySQL libraries are named differently on different distros. (1e926b4) + +##2012-01-11 - Justin Ellison +* Per @ghoneycutt, we should fail explicitly and explain why. (09af083) + +##2012-01-11 - Justin Ellison +* Removing duplicate declaration (7513d03) + +##2012-01-10 - Justin Ellison +* Use socket value from params class instead of hardcoding. (663e97c) + +##2012-01-10 - Justin Ellison +* Instead of hardcoding the config file target, pull it from mysql::params (031a47d) + +##2012-01-10 - Justin Ellison +* Moved $socket to within the case to toggle between distros. Added a $config_file variable to allow per-distro config file destinations. (360eacd) + +##2012-01-10 - Justin Ellison +* Pretty sure this is a bug, 99% of Linux distros out there won't ever hit the default. (3462e6b) + +##2012-02-09 - William Van Hevelingen +* Changed the README to use markdown (3b7dfeb) + +##2012-02-04 - Daniel Black +* (#12412) mysqltuner.pl update (b809e6f) + +##2011-11-17 - Matthias Pigulla +* (#11363) Add two missing privileges to grant: event_priv, trigger_priv (d15c9d1) + +##2011-12-20 - Jeff McCune +* (minor) Fixup typos in Modulefile metadata (a0ed6a1) + +##2011-12-19 - Carl Caum +* Only notify Exec to import sql if sql is given (0783c74) + +##2011-12-19 - Carl Caum +* (#11508) Only load sql_scripts on DB creation (e3b9fd9) + +##2011-12-13 - Justin Ellison +* Require not needed due to implicit dependencies (3058feb) + +##2011-12-13 - Justin Ellison +* Bug #11375: puppetlabs-mysql fails on CentOS/RHEL (a557b8d) + +##2011-06-03 - Dan Bode - 0.0.1 +* initial commit diff --git a/environments/production/modules/mysql/CONTRIBUTING.md b/environments/production/modules/mysql/CONTRIBUTING.md new file mode 100644 index 0000000..3c3f1e7 --- /dev/null +++ b/environments/production/modules/mysql/CONTRIBUTING.md @@ -0,0 +1,218 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% bundle exec rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% bundle exec rake spec_clean +% bundle exec rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppet.com/community/get-help) + +* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) + diff --git a/environments/production/modules/mysql/Gemfile b/environments/production/modules/mysql/Gemfile new file mode 100644 index 0000000..e8b3fa6 --- /dev/null +++ b/environments/production/modules/mysql/Gemfile @@ -0,0 +1,52 @@ +#This file is generated by ModuleSync, do not edit. + +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +def location_from_env(env, default_location = []) + if location = ENV[env] + if location =~ /^((?:git|https?)[:@][^#]*)#(.*)/ + [{ :git => $1, :branch => $2, :require => false }] + elsif location =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [location, { :require => false }] + end + else + default_location + end +end + +group :development, :unit_tests do + gem 'metadata-json-lint' + gem 'puppet_facts' + gem 'puppet-blacksmith', '>= 3.4.0' + gem 'puppetlabs_spec_helper', '>= 1.2.1' + gem 'rspec-puppet', '>= 2.3.2' + gem 'rspec-puppet-facts' + gem 'mocha', '< 1.2.0' + gem 'simplecov' + gem 'parallel_tests', '< 2.10.0' if RUBY_VERSION < '2.0.0' + gem 'parallel_tests' if RUBY_VERSION >= '2.0.0' + gem 'rubocop', '0.41.2' if RUBY_VERSION < '2.0.0' + gem 'rubocop' if RUBY_VERSION >= '2.0.0' + gem 'rubocop-rspec', '~> 1.6' if RUBY_VERSION >= '2.3.0' + gem 'json_pure', '<= 2.0.1' if RUBY_VERSION < '2.0.0' + gem 'rspec-puppet-facts' +end +group :system_tests do + gem 'beaker', *location_from_env('BEAKER_VERSION', []) if RUBY_VERSION >= '2.3.0' + gem 'beaker', *location_from_env('BEAKER_VERSION', ['< 3']) if RUBY_VERSION < '2.3.0' + gem 'beaker-pe' if RUBY_VERSION >= '2.3.0' + gem 'beaker-rspec', *location_from_env('BEAKER_RSPEC_VERSION', ['>= 3.4']) + gem 'serverspec' + gem 'beaker-puppet_install_helper' + gem 'master_manipulator' + gem 'beaker-hostgenerator', *location_from_env('BEAKER_HOSTGENERATOR_VERSION', []) +end + +gem 'facter', *location_from_env('FACTER_GEM_VERSION') +gem 'puppet', *location_from_env('PUPPET_GEM_VERSION') + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end diff --git a/environments/production/modules/mysql/LICENSE b/environments/production/modules/mysql/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/environments/production/modules/mysql/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/environments/production/modules/mysql/NOTICE b/environments/production/modules/mysql/NOTICE new file mode 100644 index 0000000..caaa8f4 --- /dev/null +++ b/environments/production/modules/mysql/NOTICE @@ -0,0 +1,18 @@ +mysql puppet module + +Copyright (C) 2013-2016 Puppet Labs, Inc. + +Puppet Labs can be contacted at: info@puppetlabs.com + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/environments/production/modules/mysql/README.md b/environments/production/modules/mysql/README.md new file mode 100644 index 0000000..290d240 --- /dev/null +++ b/environments/production/modules/mysql/README.md @@ -0,0 +1,1050 @@ +# mysql + +#### Table of Contents + +1. [Module Description - What the module does and why it is useful](#module-description) +2. [Setup - The basics of getting started with mysql](#setup) + * [Beginning with mysql](#beginning-with-mysql) +3. [Usage - Configuration options and additional functionality](#usage) + * [Customize server options](#customize-server-options) + * [Create a database](#create-a-database) + * [Customize configuration](#create-custom-configuration) + * [Work with an existing server](#work-with-an-existing-server) + * [Specify passwords](#specify-passwords) + * [Install Percona server on CentOS](#install-percona-server-on-centos) + * [Install MariaDB on Ubuntu](#install-mariadb-on-ubuntu) +4. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +5. [Limitations - OS compatibility, etc.](#limitations) +6. [Development - Guide for contributing to the module](#development) + +## Module Description + +The mysql module installs, configures, and manages the MySQL service. + +This module manages both the installation and configuration of MySQL, as well as extending Puppet to allow management of MySQL resources, such as databases, users, and grants. + +## Setup + +### Beginning with mysql + +To install a server with the default options: + +`include '::mysql::server'`. + +To customize options, such as the root +password or `/etc/my.cnf` settings, you must also pass in an override hash: + +```puppet +class { '::mysql::server': + root_password => 'strongpassword', + remove_default_accounts => true, + override_options => $override_options +} +``` + +See [**Customize Server Options**](#customize-server-options) below for examples of the hash structure for $override_options`. + +## Usage + +All interaction for the server is done via `mysql::server`. To install the client, use `mysql::client`. To install bindings, use `mysql::bindings`. + +### Customize server options + +To define server options, structure a hash structure of overrides in `mysql::server`. This hash resembles a hash in the my.cnf file: + +```puppet +$override_options = { + 'section' => { + 'item' => 'thing', + } +} +``` + +For options that you would traditionally represent in this format: + +``` +[section] +thing = X +``` + +...you can make an entry like `thing => true`, `thing => value`, or `thing => "` in the hash. Alternatively, you can pass an array, as `thing => ['value', 'value2']`, or list each `thing => value` separately on separate lines. + +You can pass a variable in the hash without setting a value for it; the variable would then use MySQL's default settings. To exclude an option from the my.cnf file --- for example, when using `override_options` to revert to a default value --- pass `thing => undef`. + +If an option needs multiple instances, pass an array. For example, + +```puppet +$override_options = { + 'mysqld' => { + 'replicate-do-db' => ['base1', 'base2'], + } +} +``` + +produces + +``` +[mysqld] +replicate-do-db = base1 +replicate-do-db = base2 +``` + +To implement version specific parameters, specify the version, such as [mysqld-5.5]. This allows one config for different versions of MySQL. + +### Create a database + +To create a database with a user and some assigned privileges: + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +``` + +To use a different resource name with exported resources: + +```puppet + @@mysql::db { "mydb_${fqdn}": + user => 'myuser', + password => 'mypass', + dbname => 'mydb', + host => ${fqdn}, + grant => ['SELECT', 'UPDATE'], + tag => $domain, +} +``` + +Then you can collect it on the remote DB server: + +```puppet +Mysql::Db <<| tag == $domain |>> +``` + +If you set the sql parameter to a file when creating a database, the file is imported into the new database. + +For large sql files, increase the `import_timeout` parameter, which defaults to 300 seconds. + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], + sql => '/path/to/sqlfile.gz', + import_cat_cmd => 'zcat', + import_timeout => 900, +} +``` + +### Customize configuration + +To add custom MySQL configuration, place additional files into `includedir`. This allows you to override settings or add additional ones, which is helpful if you don't use `override_options` in `mysql::server`. The `includedir` location is by default set to `/etc/mysql/conf.d`. + +### Work with an existing server + +To instantiate databases and users on an existing MySQL server, you need a `.my.cnf` file in `root`'s home directory. This file must specify the remote server address and credentials. For example: + +``` +[client] +user=root +host=localhost +password=secret +``` + +This module uses the `mysqld_version` fact to discover the server version being used. By default, this is set to the output of `mysqld -V`. If you're working with a remote MySQL server, you may need to set a custom fact for `mysqld_version` to ensure correct behaviour. + +When working with a remote server, do *not* use the `mysql::server` class in your Puppet manifests. + +### Specify passwords + +In addition to passing passwords as plain text, you can input them as hashes. For example: + +```puppet +mysql::db { 'mydb': + user => 'myuser', + password => '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +``` + +### Install Percona server on CentOS + +This example shows how to do a minimal installation of a Percona server on a +CentOS system. This sets up the Percona server, client, and bindings (including Perl and Python bindings). You can customize this usage and update the version as needed. + +This usage has been tested on Puppet 4.4 / CentOS 7 / Percona Server 5.7. + +**Note:** The installation of the yum repository is not part of this package +and is here only to show a full example of how you can install. + +```puppet +yumrepo { 'percona': + descr => 'CentOS $releasever - Percona', + baseurl => 'http://repo.percona.com/centos/$releasever/os/$basearch/', + gpgkey => 'http://www.percona.com/downloads/percona-release/RPM-GPG-KEY-percona', + enabled => 1, + gpgcheck => 1, +} + +class {'mysql::server': + package_name => 'Percona-Server-server-57', + package_ensure => '5.7.11-4.1.el7', + service_name => 'mysql', + config_file => '/etc/my.cnf', + includedir => '/etc/my.cnf.d', + root_password => 'PutYourOwnPwdHere', + override_options => { + mysqld => { + log-error => '/var/log/mysqld.log', + pid-file => '/var/run/mysqld/mysqld.pid', + }, + mysqld_safe => { + log-error => '/var/log/mysqld.log', + }, + } +} + +# Note: Installing Percona-Server-server-57 also installs Percona-Server-client-57. +# This shows how to install the Percona MySQL client on its own +class {'mysql::client': + package_name => 'Percona-Server-client-57', + package_ensure => '5.7.11-4.1.el7', +} + +# These packages are normally installed along with Percona-Server-server-57 +# If you needed to install the bindings, however, you could do so with this code +class { 'mysql::bindings': + client_dev_package_name => 'Percona-Server-shared-57', + client_dev_package_ensure => '5.7.11-4.1.el7', + client_dev => true, + daemon_dev_package_name => 'Percona-Server-devel-57', + daemon_dev_package_ensure => '5.7.11-4.1.el7', + daemon_dev => true, + perl_enable => true, + perl_package_name => 'perl-DBD-MySQL', + python_enable => true, + python_package_name => 'MySQL-python', +} + +# Dependencies definition +Yumrepo['percona']-> +Class['mysql::server'] + +Yumrepo['percona']-> +Class['mysql::client'] + +Yumrepo['percona']-> +Class['mysql::bindings'] +``` + +### Install MariaDB on Ubuntu + +#### Optional: Install the MariaDB official repo + +In this example, we'll use the latest stable (currently 10.1) from the official MariaDB repository, not the one from the distro repository. You could instead use the package from the Ubuntu repository. Make sure you use the repository corresponding to the version you want. + +**Note:** `sfo1.mirrors.digitalocean.com` is one of many mirrors available. You can use any official mirror. + +``` +include apt + +apt::source { 'mariadb': + location => 'http://sfo1.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu', + release => $::lsbdistcodename, + repos => 'main', + key => { + id => '199369E5404BD5FC7D2FE43BCBCB082A1BB943DB', + server => 'hkp://keyserver.ubuntu.com:80', + }, + include => { + src => false, + deb => true, + }, +} +``` + +#### Install the MariaDB server + +This example shows MariaDB server installation on Ubuntu Trusty. Adjust the version and the parameters of `my.cnf` as needed. All parameters of the `my.cnf` can be defined using the `override_options` parameter. + +The folders `/var/log/mysql` and `/var/run/mysqld` are created automatically, but if you are using other custom folders, they should exist as prerequisites for this code. + +All the values set here are an example of a working minimal configuration. + +Specify the version of the package you want with the `package_ensure` parameter. + +``` +class {'::mysql::server': + package_name => 'mariadb-server', + package_ensure => '10.1.14+maria-1~trusty', + service_name => 'mysql', + root_password => 'AVeryStrongPasswordUShouldEncrypt!', + override_options => { + mysqld => { + 'log-error' => '/var/log/mysql/mariadb.log', + 'pid-file' => '/var/run/mysqld/mysqld.pid', + }, + mysqld_safe => { + 'log-error' => '/var/log/mysql/mariadb.log', + }, + } +} + +# Dependency management. Only use that part if you are installing the repository +# as shown in the Preliminary step of this example. +Apt::Source['mariadb'] ~> +Class['apt::update'] -> +Class['::mysql::server'] + +``` + +#### Install the MariaDB client + +This example shows how to install the MariaDB client and all of the bindings at once. You can do this installation separately from the server installation. + +Specify the version of the package you want with the `package_ensure` parameter. + +``` +class {'::mysql::client': + package_name => 'mariadb-client', + package_ensure => '10.1.14+maria-1~trusty', + bindings_enable => true, +} + +# Dependency management. Only use that part if you are installing the repository +# as shown in the Preliminary step of this example. +Apt::Source['mariadb'] ~> +Class['apt::update'] -> +Class['::mysql::client'] +``` + +## Reference + +### Classes + +#### Public classes + +* [`mysql::server`](#mysqlserver): Installs and configures MySQL. +* [`mysql::server::monitor`](#mysqlservermonitor): Sets up a monitoring user. +* [`mysql::server::mysqltuner`](#mysqlservermysqltuner): Installs MySQL tuner script. +* [`mysql::server::backup`](#mysqlserverbackup): Sets up MySQL backups via cron. +* [`mysql::bindings`](#mysqlbindings): Installs various MySQL language bindings. +* [`mysql::client`](#mysqlclient): Installs MySQL client (for non-servers). + +#### Private classes + +* `mysql::server::install`: Installs packages. +* `mysql::server::installdb`: Implements setup of mysqld data directory (e.g. /var/lib/mysql) +* `mysql::server::config`: Configures MYSQL. +* `mysql::server::service`: Manages service. +* `mysql::server::account_security`: Deletes default MySQL accounts. +* `mysql::server::root_password`: Sets MySQL root password. +* `mysql::server::providers`: Creates users, grants, and databases. +* `mysql::bindings::client_dev`: Installs MySQL client development package. +* `mysql::bindings::daemon_dev`: Installs MySQL daemon development package. +* `mysql::bindings::java`: Installs Java bindings. +* `mysql::bindings::perl`: Installs Perl bindings. +* `mysql::bindings::php`: Installs PHP bindings. +* `mysql::bindings::python`: Installs Python bindings. +* `mysql::bindings::ruby`: Installs Ruby bindings. +* `mysql::client::install`: Installs MySQL client. +* `mysql::backup::mysqldump`: Implements mysqldump backups. +* `mysql::backup::mysqlbackup`: Implements backups with Oracle MySQL Enterprise Backup. +* `mysql::backup::xtrabackup`: Implements backups with XtraBackup from Percona. + +### Parameters + +#### mysql::server + +##### `create_root_user` + +Whether root user should be created. Valid values are true, false. Defaults to true. + +This is useful for a cluster setup with Galera. The root user has to be created only once. You can set this parameter true on one node and set it to false on the remaining nodes. + +##### `create_root_my_cnf` + +Whether to create `/root/.my.cnf`. Valid values are true, false. Defaults to true. + +`create_root_my_cnf` allows creation of `/root/.my.cnf` independently of `create_root_user`. You can use this for a cluster setup with Galera where you want `/root/.my.cnf` to exist on all nodes. + +##### `root_password` + +The MySQL root password. Puppet attempts to set the root password and update `/root/.my.cnf` with it. + +This is required if `create_root_user` or `create_root_my_cnf` are true. If `root_password` is 'UNSET', then `create_root_user` and `create_root_my_cnf` are assumed to be false --- that is, the MySQL root user and `/root/.my.cnf` are not created. + +Password changes are supported; however, the old password must be set in `/root/.my.cnf`. Effectively, Puppet uses the old password, configured in `/root/my.cnf`, to set the new password in MySQL, and then updates `/root/.my.cnf` with the new password. + +##### `old_root_password` + +This parameter no longer does anything. It exists only for backwards compatibility. See the `root_password` parameter above for details on changing the root password. + +##### `override_options` + +Specifies override options to pass into MySQL. Structured like a hash in the my.cnf file: + +```puppet +$override_options = { + 'section' => { + 'item' => 'thing', + } +} +``` + +See [**Customize Server Options**](#customize-server-options) above for usage details. + +##### `config_file` + +The location, as a path, of the MySQL configuration file. + +##### `manage_config_file` + +Whether the MySQL configuration file should be managed. Valid values are true, false. Defaults to true. + +##### `includedir` +The location, as a path, of !includedir for custom configuration overrides. + +##### `install_options` +Passes [install_options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) array to managed package resources. You must pass the appropriate options for the specified package manager. + +##### `purge_conf_dir` + +Whether the `includedir` directory should be purged. Valid values are true, false. Defaults to false. + +##### `restart` + +Whether the service should be restarted when things change. Valid values are true, false. Defaults to false. + +##### `root_group` + +The name of the group used for root. Can be a group name or a group ID. See more about the [`group` file attribute](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). + +##### `mysql_group` + +The name of the group of the MySQL daemon user. Can be a group name or a group ID. See more about the [`group` file attribute](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). + +##### `package_ensure` + +Whether the package exists or should be a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Defaults to 'present'. + +##### `package_manage` + +Whether to manage the MySQL server package. Defaults to true. + +##### `package_name` + +The name of the MySQL server package to install. + +##### `remove_default_accounts` + +Specifies whether to automatically include `mysql::server::account_security`. Valid values are true, false. Defaults to false. + +##### `service_enabled` + +Specifies whether the service should be enabled. Valid values are true, false. Defaults to true. + +##### `service_manage` + +Specifies whether the service should be managed. Valid values are true, false. Defaults to true. + +##### `service_name` + +The name of the MySQL server service. Defaults are OS dependent, defined in params.pp. + +##### `service_provider` + +The provider to use to manage the service. For Ubuntu, defaults to 'upstart'; otherwise, default is undefined. + +##### `users` + +Optional hash of users to create, which are passed to [mysql_user](#mysql_user). + +``` +users => { + 'someuser@localhost' => { + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', + password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', + tls_options => ['NONE'], + }, +} +``` + +##### `grants` + +Optional hash of grants, which are passed to [mysql_grant](#mysql_grant). + +``` +grants => { + 'someuser@localhost/somedb.*' => { + ensure => 'present', + options => ['GRANT'], + privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], + table => 'somedb.*', + user => 'someuser@localhost', + }, +} +``` + +##### `databases` + +Optional hash of databases to create, which are passed to [mysql_database](#mysql_database). + +``` +databases => { + 'somedb' => { + ensure => 'present', + charset => 'utf8', + }, +} +``` + +#### mysql::server::backup + +##### `backupuser` + +MySQL user to create for backups. + +##### `backuppassword` + +MySQL user password for backups. + +##### `backupdir` + +Directory in which to store backups. + +##### `backupdirmode` + +Permissions applied to the backup directory. This parameter is passed directly +to the `file` resource. + +##### `backupdirowner` + +Owner for the backup directory. This parameter is passed directly to the `file` +resource. + +##### `backupdirgroup` + +Group owner for the backup directory. This parameter is passed directly to the +`file` resource. + +##### `backupcompress` + +Whether backups should be compressed. Valid values are true, false. Defaults to true. + +##### `backuprotate` + +How many days to keep backups. Valid value is an integer. Defaults to '30'. + +##### `delete_before_dump` + +Whether to delete old .sql files before backing up. Setting to true deletes old files before backing up, while setting to false deletes them after backup. Valid values are true, false. Defaults to false. + +##### `backupdatabases` + +Specifies an array of databases to back up. + +##### `file_per_database` + +Whether a separate file be used per database. Valid values are true, false. Defaults to false. + +##### `include_routines` + +Whether or not to include routines for each database when doing a `file_per_database` backup. Defaults to false. + +##### `include_triggers` + +Whether or not to include triggers for each database when doing a `file_per_database` backup. Defaults to false. + +##### `ensure` + +Allows you to remove the backup scripts. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `execpath` + +Allows you to set a custom PATH should your MySQL installation be non-standard places. Defaults to `/usr/bin:/usr/sbin:/bin:/sbin`. + +##### `time` + +An array of two elements to set the backup time. Allows ['23', '5'] (i.e., 23:05) or ['3', '45'] (i.e., 03:45) for HH:MM times. + +##### `postscript` + +A script that is executed when the backup is finished. This could be used to (r)sync the backup to a central store. This script can be either a single line that is directly executed or a number of lines supplied as an array. It could also be one or more externally managed (executable) files. + +##### `prescript` + +A script that is executed before the backup begins. + +##### `provider` + +Sets the server backup implementation. Valid values are: + +* `mysqldump`: Implements backups with mysqldump. Backup type: Logical. This is the default value. +* `mysqlbackup`: Implements backups with MySQL Enterprise Backup from Oracle. Backup type: Physical. To use this type of backup, you'll need the `meb` package, which is available in RPM and TAR formats from Oracle. For Ubuntu, you can use [meb-deb](https://github.com/dveeden/meb-deb) to create a package from an official tarball. +* `xtrabackup`: Implements backups with XtraBackup from Percona. Backup type: Physical. + +##### `maxallowedpacket` + +Defines the maximum SQL statement size for the backup dump script. The default value is 1MB, as this is the default MySQL Server value. + +#### mysql::server::monitor + +##### `mysql_monitor_username` + +The username to create for MySQL monitoring. + +##### `mysql_monitor_password` + +The password to create for MySQL monitoring. + +##### `mysql_monitor_hostname` + +The hostname from which the monitoring user requests are allowed access. + +#### mysql::server::mysqltuner + +**Note**: If you're using this class on a non-network-connected system, you must download the mysqltuner.pl script and have it hosted somewhere accessible via `http(s)://`, `puppet://`, `ftp://`, or a fully qualified file path. + +##### `ensure` + +Ensures that the resource exists. Valid values are `present`, `absent`. Defaults to `present`. + +##### `version` + +The version to install from the major/MySQLTuner-perl github repository. Must be a valid tag. Defaults to 'v1.3.0'. + +##### `source` + +Specifies the source. If not specified, defaults to `https://github.com/major/MySQLTuner-perl/raw/${version}/mysqltuner.pl` + +#### mysql::bindings + +##### `client_dev` + +Specifies whether `::mysql::bindings::client_dev` should be included. Valid values are true', false. Defaults to false. + +##### `daemon_dev` + +Specifies whether `::mysql::bindings::daemon_dev` should be included. Valid values are true, false. Defaults to false. + +##### `java_enable` + +Specifies whether `::mysql::bindings::java` should be included. Valid values are true, false. Defaults to false. + +##### `perl_enable` + +Specifies whether `mysql::bindings::perl` should be included. Valid values are true, false. Defaults to false. + +##### `php_enable` + +Specifies whether `mysql::bindings::php` should be included. Valid values are true, false. Defaults to false. + +##### `python_enable` + +Specifies whether `mysql::bindings::python` should be included. Valid values are true, false. Defaults to false. + +##### `ruby_enable` + +Specifies whether `mysql::bindings::ruby` should be included. Valid values are true, false. Defaults to false. + +##### `install_options` + +Passes `install_options` array to managed package resources. You must pass the [appropriate options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) for the package manager(s). + +##### `client_dev_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `client_dev => true`. + +##### `client_dev_package_name` + +The name of the client_dev package to install. Only applies if `client_dev => true`. + +##### `client_dev_package_provider` + +The provider to use to install the client_dev package. Only applies if `client_dev => true`. + +##### `daemon_dev_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `daemon_dev => true`. + +##### `daemon_dev_package_name` + +The name of the daemon_dev package to install. Only applies if `daemon_dev => true`. + +##### `daemon_dev_package_provider` + +The provider to use to install the daemon_dev package. Only applies if `daemon_dev => true`. + +##### `java_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `java_enable => true`. + +##### `java_package_name` + +The name of the Java package to install. Only applies if `java_enable => true`. + +##### `java_package_provider` + +The provider to use to install the Java package. Only applies if `java_enable => true`. + +##### `perl_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `perl_enable => true`. + +##### `perl_package_name` + +The name of the Perl package to install. Only applies if `perl_enable => true`. + +##### `perl_package_provider` + +The provider to use to install the Perl package. Only applies if `perl_enable => true`. + +##### `php_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `php_enable => true`. + +##### `php_package_name` + +The name of the PHP package to install. Only applies if `php_enable => true`. + +##### `python_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `python_enable => true`. + +##### `python_package_name` + +The name of the Python package to install. Only applies if `python_enable => true`. + +##### `python_package_provider` + +The provider to use to install the PHP package. Only applies if `python_enable => true`. + +##### `ruby_package_ensure` + +Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `ruby_enable => true`. + +##### `ruby_package_name` + +The name of the Ruby package to install. Only applies if `ruby_enable => true`. + +##### `ruby_package_provider` + +What provider should be used to install the package. + +#### mysql::client + +##### `bindings_enable` + +Whether to automatically install all bindings. Valid values are true, false. Default to false. + +##### `install_options` +Array of install options for managed package resources. You must pass the appropriate options for the package manager. + +##### `package_ensure` + +Whether the MySQL package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. + +##### `package_manage` + +Whether to manage the MySQL client package. Defaults to true. + +##### `package_name` + +The name of the MySQL client package to install. + +### Defines + +#### mysql::db + +``` +mysql_database { 'information_schema': + ensure => 'present', + charset => 'utf8', + collate => 'utf8_swedish_ci', +} +mysql_database { 'mysql': + ensure => 'present', + charset => 'latin1', + collate => 'latin1_swedish_ci', +} +``` + +##### `user` + +The user for the database you're creating. + +##### `password` + +The password for $user for the database you're creating. + +##### `dbname` + +The name of the database to create. Defaults to $name. + +##### `charset` + +The character set for the database. Defaults to 'utf8'. + +##### `collate` + +The collation for the database. Defaults to 'utf8_general_ci'. + +##### `host` + +The host to use as part of user@host for grants. Defaults to 'localhost'. + +##### `grant` + +The privileges to be granted for user@host on the database. Defaults to 'ALL'. + +##### `sql` + +The path to the sqlfile you want to execute. This can be single file specified as string, or it can be an array of strings. Defaults to undef. + +##### `enforce_sql` + +Specifies whether executing the sqlfiles should happen on every run. If set to false, sqlfiles only run once. Valid values are true, false. Defaults to false. + +##### `ensure` + +Specifies whether to create the database. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `import_timeout` + +Timeout, in seconds, for loading the sqlfiles. Defaults to '300'. + +##### `import_cat_cmd` + +Command to read the sqlfile for importing the database. Useful for compressed sqlfiles. For example, you can use 'zcat' for .gz files. Defaults to 'cat'. + +### Types + +#### mysql_database + +`mysql_database` creates and manages databases within MySQL. + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +The name of the MySQL database to manage. + +##### `charset` + +The CHARACTER SET setting for the database. Defaults to ':utf8'. + +##### `collate` + +The COLLATE setting for the database. Defaults to ':utf8_general_ci'. + +#### mysql_user + +Creates and manages user grants within MySQL. + +``` +mysql_user { 'root@127.0.0.1': + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', +} +``` + +You can also specify an authentication plugin. + +``` +mysql_user{ 'myuser'@'localhost': + ensure => 'present', + plugin => 'unix_socket', +} +``` + +TLS options can be specified for a user. +``` +mysql_user{ 'myuser'@'localhost': + ensure => 'present', + tls_options => ['SSL'], +} +``` + +##### `name` + +The name of the user, as 'username@hostname' or username@hostname. + +##### `password_hash` + +The user's password hash of the user. Use mysql_password() for creating such a hash. + +##### `max_user_connections` + +Maximum concurrent connections for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_connections_per_hour` + +Maximum connections per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_queries_per_hour` + +Maximum queries per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `max_updates_per_hour` + +Maximum updates per hour for the user. Must be an integer value. A value of '0' specifies no (or global) limit. + +##### `tls_options` + +SSL-related options for a MySQL account, using one or more tls_option values. 'NONE' specifies that the account has no TLS options enforced, and the available options are 'SSL', 'X509', 'CIPHER *cipher*', 'ISSUER *issuer*', 'SUBJECT *subject*'; as stated in the MySQL documentation. + + +#### mysql_grant + +`mysql_grant` creates grant permissions to access databases within MySQL. To create grant permissions to access databases with MySQL, use it you must create the title of the resource as shown below, following the pattern of `username@hostname/database.table`: + +``` +mysql_grant { 'root@localhost/*.*': + ensure => 'present', + options => ['GRANT'], + privileges => ['ALL'], + table => '*.*', + user => 'root@localhost', +} +``` + +It is possible to specify privileges down to the column level: + +``` +mysql_grant { 'root@localhost/mysql.user': + ensure => 'present', + privileges => ['SELECT (Host, User)'], + table => 'mysql.user', + user => 'root@localhost', +} +``` + +To revoke GRANT privilege specify ['NONE']. + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +Name to describe the grant. Must in a 'user/table' format. + +##### `privileges` + +Privileges to grant the user. + +##### `table` + +The table to which privileges are applied. + +##### `user` + +User to whom privileges are granted. + +##### `options` + +MySQL options to grant. Optional. + +#### mysql_plugin + +`mysql_plugin` can be used to load plugins into the MySQL Server. + +``` +mysql_plugin { 'auth_socket': + ensure => 'present', + soname => 'auth_socket.so', +} +``` + +##### `ensure` + +Whether the resource is present. Valid values are 'present', 'absent'. Defaults to 'present'. + +##### `name` + +The name of the MySQL plugin to manage. + +##### `soname` + +The library file name. + +#### `mysql_datadir` + +Initializes the MySQL data directory with version specific code. Pre MySQL 5.7.6 +it uses mysql_install_db. After MySQL 5.7.6 it uses mysqld --initialize-insecure. + +Insecure initialization is needed, as mysqld version 5.7 introduced "secure by default" mode. +This means MySQL generates a random password and writes it to STDOUT. This means puppet +can never accesss the database server afterwards, as no credencials are available. + +This type is an internal type and should not be called directly. + +### Facts + +#### `mysql_version` + +Determines the MySQL version by parsing the output from `mysql --version` + +#### `mysql_server_id` + +Generates a unique id, based on the node's MAC address, which can be used as +`server_id`. This fact will *always* return `0` on nodes that have only +loopback interfaces. Because those nodes aren't connected to the outside world, this shouldn't cause any conflicts. + +## Limitations + +This module has been tested on: + +* RedHat Enterprise Linux 5, 6, 7 +* Debian 6, 7, 8 +* CentOS 5, 6, 7 +* Ubuntu 10.04, 12.04, 14.04, 16.04 +* Scientific Linux 5, 6 +* SLES 11 + +Testing on other platforms has been minimal and cannot be guaranteed. + +**Note:** The mysqlbackup.sh does not work and is not supported on MySQL 5.7 and greater. + +## Development + +Puppet Labs modules on the Puppet Forge are open projects, and community +contributions are essential for keeping them great. We can't access the +huge number of platforms and myriad of hardware, software, and deployment +configurations that Puppet is intended to serve. + +We want to keep it as easy as possible to contribute changes so that our +modules work in your environment. There are a few guidelines that we need +contributors to follow so that we can have a chance of keeping on top of things. + +Check out our the complete [module contribution guide](https://docs.puppetlabs.com/forge/contributing.html). + +### Authors + +This module is based on work by David Schmitt. The following contributors have contributed to this module (beyond Puppet Labs): + +* Larry Ludwig +* Christian G. Warden +* Daniel Black +* Justin Ellison +* Lowe Schmidt +* Matthias Pigulla +* William Van Hevelingen +* Michael Arnold +* Chris Weyl +* Daniël van Eeden +* Jan-Otto Kröpke diff --git a/environments/production/modules/mysql/Rakefile b/environments/production/modules/mysql/Rakefile new file mode 100644 index 0000000..3e8d4cb --- /dev/null +++ b/environments/production/modules/mysql/Rakefile @@ -0,0 +1,38 @@ +require 'puppet_blacksmith/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +require 'puppetlabs_spec_helper/rake_tasks' + +PuppetLint.configuration.send('relative') +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_single_quote_string_with_variables') + +desc 'Generate pooler nodesets' +task :gen_nodeset do + require 'beaker-hostgenerator' + require 'securerandom' + require 'fileutils' + + agent_target = ENV['TEST_TARGET'] + if ! agent_target + STDERR.puts 'TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat-64default."' + agent_target = 'redhat-64default.' + end + + master_target = ENV['MASTER_TEST_TARGET'] + if ! master_target + STDERR.puts 'MASTER_TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat7-64mdcl"' + master_target = 'redhat7-64mdcl' + end + + targets = "#{master_target}-#{agent_target}" + cli = BeakerHostGenerator::CLI.new([targets]) + nodeset_dir = "tmp/nodesets" + nodeset = "#{nodeset_dir}/#{targets}-#{SecureRandom.uuid}.yaml" + FileUtils.mkdir_p(nodeset_dir) + File.open(nodeset, 'w') do |fh| + fh.print(cli.execute) + end + puts nodeset +end diff --git a/environments/production/modules/mysql/TODO b/environments/production/modules/mysql/TODO new file mode 100644 index 0000000..3913293 --- /dev/null +++ b/environments/production/modules/mysql/TODO @@ -0,0 +1,8 @@ +The best that I can tell is that this code traces back to David Schmitt. It has been forked many times since then :) + +1. you cannot add databases to an instance that has a root password +2. you have to specify username as USER@BLAH or it cannot be found +3. mysql_grant does not complain if user does not exist +4. Needs support for pre-seeding on debian +5. the types may need to take user/password +6. rather or not to configure /etc/.my.cnf should be configurable diff --git a/environments/production/modules/mysql/checksums.json b/environments/production/modules/mysql/checksums.json new file mode 100644 index 0000000..a07fb79 --- /dev/null +++ b/environments/production/modules/mysql/checksums.json @@ -0,0 +1,115 @@ +{ + "CHANGELOG.md": "b68d316974b7228a92b8a8c3b65bb9a4", + "CONTRIBUTING.md": "b78f71c1104f00538d50ad2775f58e95", + "Gemfile": "fda543906c8c75b06ea37c4d31b95292", + "LICENSE": "3b83ef96387f14655fc854ddc3c6bd57", + "NOTICE": "d43aacde256c8e55a50e2425e25b3a8a", + "README.md": "b4969d0b8649dd0f9c105ff22b66a871", + "Rakefile": "6db744f1deed7ae0746abe59e3d50863", + "TODO": "88ca4024a37992b46c34cb46e4ac39e6", + "examples/backup.pp": "a61c6f34f153a323209faf25948737f5", + "examples/bindings.pp": "35a8387f5c55fa2e479c513a67918674", + "examples/java.pp": "0ad9de4f9f2c049642bcf08124757085", + "examples/mysql_database.pp": "107ee8793f7b4a12cfca32eddccc6bbd", + "examples/mysql_db.pp": "55d2d603f9fb8ab3c8a781d08119aa69", + "examples/mysql_grant.pp": "cd42336a6c7b2d27f5d5d6d0e310ee1a", + "examples/mysql_plugin.pp": "4f86c988a99b131e736501a309604e94", + "examples/mysql_user.pp": "0b76964aebb01714745548e0f2f32fd3", + "examples/perl.pp": "454f14dc4492fcf04afbe81b2776917e", + "examples/python.pp": "355a7e1ea3978a8fd290b5bc28b63808", + "examples/ruby.pp": "a6ae0381aacc5a8d2c403e606c6df0f0", + "examples/server/account_security.pp": "375442b7886c01b42fbf75a1fcb31822", + "examples/server/config.pp": "659b7c40e9b55634721b3c33a8c6da98", + "examples/server.pp": "72e22552a95b9a5e4a349dbfc13639dc", + "lib/facter/mysql_server_id.rb": "10de205ca9dc83b68cf63b52d97346ec", + "lib/facter/mysql_version.rb": "e30648f3d394a1cbdd4e54eb5c28d28c", + "lib/facter/mysqld_version.rb": "720f447c97dd4ce099006cd5391ade57", + "lib/puppet/parser/functions/mysql_deepmerge.rb": "2b5040ee8cd75a81cf881851e922833a", + "lib/puppet/parser/functions/mysql_dirname.rb": "820ba09a6a0df639da560b41cb31242f", + "lib/puppet/parser/functions/mysql_password.rb": "365a0ba55f0d04d0f5d56abcd577ec7d", + "lib/puppet/parser/functions/mysql_strip_hash.rb": "3efe69f1eb189b2913e178b8472aaede", + "lib/puppet/provider/mysql.rb": "2696893220da9d341d5826c9e9d4ca19", + "lib/puppet/provider/mysql_database/mysql.rb": "5cf9b8044df4ec72a726b5f781c84f8f", + "lib/puppet/provider/mysql_datadir/mysql.rb": "7c256b40cf96bdafde9c762ab867de80", + "lib/puppet/provider/mysql_grant/mysql.rb": "3630023349a4c8f06932938a26aaa2e6", + "lib/puppet/provider/mysql_plugin/mysql.rb": "49128d2a9a547c4945735bfd75fd6d6c", + "lib/puppet/provider/mysql_user/mysql.rb": "dc09eef4e1d879ffe4ee8eb9a7b3026c", + "lib/puppet/type/mysql_database.rb": "438eafbfecc30605462a220d92cf9603", + "lib/puppet/type/mysql_datadir.rb": "f19e548062e81f424646c193b086aaf6", + "lib/puppet/type/mysql_grant.rb": "afcc65b0394a23e95b8bf932bc6110b4", + "lib/puppet/type/mysql_plugin.rb": "0a52cead6c9283c9aa14ea71bdfa80bd", + "lib/puppet/type/mysql_user.rb": "8cc3c0851c06d01b310a7f551945e696", + "manifests/backup/mysqlbackup.pp": "66441b167e682689df21a4b37af175e8", + "manifests/backup/mysqldump.pp": "d858a7dda445c45952f6a7f029d41189", + "manifests/backup/xtrabackup.pp": "ebf4f4765fbbedbdc49acad9dd539a6b", + "manifests/bindings/client_dev.pp": "80753f1bf48de71d2ca1a6cfda4830be", + "manifests/bindings/daemon_dev.pp": "94d2d74afc2b247593877cebd548ed76", + "manifests/bindings/java.pp": "556b743dc162d2f3264708b0b7ba0328", + "manifests/bindings/perl.pp": "4ecbd448ceac580a07df34b5d3e24837", + "manifests/bindings/php.pp": "641139f1f27085b8e72ac73fb8bc39d8", + "manifests/bindings/python.pp": "ef9e674237eddd7e98ab307131b11069", + "manifests/bindings/ruby.pp": "32b0a32161f7a5b50562cb8e154207d9", + "manifests/bindings.pp": "8becf04105d44910f12986a58cd566f7", + "manifests/client/install.pp": "7b5810404cc9411ec38de404749f1266", + "manifests/client.pp": "c350bd6d108acb03a87b860b14d225ef", + "manifests/db.pp": "fada45e451ec7563dab714d12b9e330b", + "manifests/params.pp": "816c5cf78525ab2917da4ce1a888dfe6", + "manifests/server/account_security.pp": "36a293eec189ec1bdfe92b370d087085", + "manifests/server/backup.pp": "6ed4f8c9aac5d7f17bf5c10fceddde41", + "manifests/server/binarylog.pp": "d61af28d0f9c07cfa23c63a7cdd364bf", + "manifests/server/config.pp": "650f0ba40b9148e66f910465e63b1170", + "manifests/server/install.pp": "3cbbf313b940b582e73c229f4a8c0720", + "manifests/server/installdb.pp": "ccc13d3cbeb78b0c144006a256e40079", + "manifests/server/monitor.pp": "3810b5d756a3661e92d709115982cd59", + "manifests/server/mysqltuner.pp": "24d04e0b24cf3b31b6798bc76eed32b6", + "manifests/server/providers.pp": "87a019dce5bbb6b18c9aa61b5f99134c", + "manifests/server/root_password.pp": "5790e04defe2a64d145975abdfcfd3ac", + "manifests/server/service.pp": "e9d4256c2830a8b300822616c408ce95", + "manifests/server.pp": "3f3915a73ff075eca009f5656b225e6d", + "metadata.json": "2cc79c0e6902c12c1b33eca2e192ea29", + "spec/acceptance/mysql_backup_spec.rb": "ef6f0d033e87a8a9f8457f89ad8ea118", + "spec/acceptance/mysql_db_spec.rb": "c093f0d25c7000713b444706b6cfd1c7", + "spec/acceptance/mysql_helper.rb": "b95d8fd15325f1ece1cd652dc60c7608", + "spec/acceptance/mysql_server_spec.rb": "b3362859a6cdc38cfabf1c5a63a4fc57", + "spec/acceptance/nodesets/centos-7-x64.yml": "a713f3abd3657f0ae2878829badd23cd", + "spec/acceptance/nodesets/debian-8-x64.yml": "d2d2977900989f30086ad251a14a1f39", + "spec/acceptance/nodesets/default.yml": "b42da5a1ea0c964567ba7495574b8808", + "spec/acceptance/nodesets/docker/centos-7.yml": "8a3892807bdd62306ae4774f41ba11ae", + "spec/acceptance/nodesets/docker/debian-8.yml": "ac8e871d1068c96de5e85a89daaec6df", + "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "dc42ee922a96908d85b8f0f08203ce58", + "spec/acceptance/types/mysql_database_spec.rb": "047db055103fa9782bc6714297eb2a09", + "spec/acceptance/types/mysql_grant_spec.rb": "216d93f0937dbdb5fc58a61bd2150d62", + "spec/acceptance/types/mysql_plugin_spec.rb": "107f32d267daa847996ff992ccec8c06", + "spec/acceptance/types/mysql_user_spec.rb": "5d04dc3b3ffd0778f83038833ef72bf2", + "spec/classes/graceful_failures_spec.rb": "91481f693c3f71dff22524189438bcc6", + "spec/classes/mycnf_template_spec.rb": "07fc36d51b592eba1f94d39208c2701f", + "spec/classes/mysql_bindings_spec.rb": "14c5abbe4ae20278d1a4a02254bf312b", + "spec/classes/mysql_client_spec.rb": "b1fa7a5a06585697f7100d1891d359ed", + "spec/classes/mysql_server_account_security_spec.rb": "97b6caf04a7a75a88e58728edcfc11cb", + "spec/classes/mysql_server_backup_spec.rb": "964357b46daa237c92cc61b88a20093b", + "spec/classes/mysql_server_monitor_spec.rb": "aef3aa73265b42b66b963a09e5f32f1f", + "spec/classes/mysql_server_mysqltuner_spec.rb": "55ff6dad45a8dfef97d52dff0b111a36", + "spec/classes/mysql_server_spec.rb": "99806ad51b124bf8ca9dad201962b743", + "spec/defines/mysql_db_spec.rb": "afb2d6cfa051dd4d8301d7aede1aa1c4", + "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c", + "spec/spec_helper.rb": "b2db3bc02b4ac2fd5142a6621c641b07", + "spec/spec_helper_acceptance.rb": "2d7d8e8a6d1225ba6bf57a4f05dfff0c", + "spec/spec_helper_local.rb": "05db024c91eb5d5141b67d97b01a58b4", + "spec/unit/facter/mysql_server_id_spec.rb": "4a01f2323a400c82d89f37847c3c9611", + "spec/unit/facter/mysql_version_spec.rb": "f593c725b83a7bbf1f0fdc13bf1531df", + "spec/unit/facter/mysqld_version_spec.rb": "f5495c63a64d21be175d329cc5a3a8cd", + "spec/unit/puppet/functions/mysql_deepmerge_spec.rb": "f6102e1f82fb9f4aa38cbf3955ee5973", + "spec/unit/puppet/functions/mysql_password_spec.rb": "8fe68dc4ec13fc6ade18defe54cac8d4", + "spec/unit/puppet/provider/mysql_database/mysql_spec.rb": "98a799433ac10b237ac3ad27441610d4", + "spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb": "914bac73bb9841ec7d7041488a55d442", + "spec/unit/puppet/provider/mysql_user/mysql_spec.rb": "174e7e3a97529fdcc2e6ba24e65cf8d2", + "spec/unit/puppet/type/mysql_database_spec.rb": "9fa0d390c2e15c8a52fc2b981e2a63fc", + "spec/unit/puppet/type/mysql_grant_spec.rb": "1a003358dc88a74ef21fb33fc71debef", + "spec/unit/puppet/type/mysql_plugin_spec.rb": "7ebacf228dfd0c60811bdf8a28a329ff", + "spec/unit/puppet/type/mysql_user_spec.rb": "e902ad1b9578dce35e13750c7967ef1d", + "templates/meb.cnf.erb": "b6422b19ee97b8a2883bfac44fdc0292", + "templates/my.cnf.erb": "535d2ff37fea6b11ad928224965143d3", + "templates/my.cnf.pass.erb": "11f80afb0993a436f074a43f70733999", + "templates/mysqlbackup.sh.erb": "0cc74138c7a5dd5d3864f94227717bd4", + "templates/xtrabackup.sh.erb": "219b487c4faad80a897cda6973b81741" +} \ No newline at end of file diff --git a/environments/production/modules/mysql/examples/backup.pp b/environments/production/modules/mysql/examples/backup.pp new file mode 100644 index 0000000..4bdf804 --- /dev/null +++ b/environments/production/modules/mysql/examples/backup.pp @@ -0,0 +1,9 @@ +class { 'mysql::server': + root_password => 'password' +} + +class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', +} diff --git a/environments/production/modules/mysql/examples/bindings.pp b/environments/production/modules/mysql/examples/bindings.pp new file mode 100644 index 0000000..e4e325c --- /dev/null +++ b/environments/production/modules/mysql/examples/bindings.pp @@ -0,0 +1,3 @@ +class { 'mysql::bindings': + php_enable => true, +} diff --git a/environments/production/modules/mysql/examples/java.pp b/environments/production/modules/mysql/examples/java.pp new file mode 100644 index 0000000..0fc009a --- /dev/null +++ b/environments/production/modules/mysql/examples/java.pp @@ -0,0 +1 @@ +class { 'mysql::java':} diff --git a/environments/production/modules/mysql/examples/mysql_database.pp b/environments/production/modules/mysql/examples/mysql_database.pp new file mode 100644 index 0000000..47a328c --- /dev/null +++ b/environments/production/modules/mysql/examples/mysql_database.pp @@ -0,0 +1,17 @@ +class { 'mysql::server': + root_password => 'password' +} +mysql::db{ ['test1', 'test2', 'test3']: + ensure => present, + charset => 'utf8', + require => Class['mysql::server'], +} +mysql::db{ 'test4': + ensure => present, + charset => 'latin1', +} +mysql::db{ 'test5': + ensure => present, + charset => 'binary', + collate => 'binary', +} diff --git a/environments/production/modules/mysql/examples/mysql_db.pp b/environments/production/modules/mysql/examples/mysql_db.pp new file mode 100644 index 0000000..43c619f --- /dev/null +++ b/environments/production/modules/mysql/examples/mysql_db.pp @@ -0,0 +1,17 @@ +class { 'mysql::server': + root_password => 'password' +} +mysql::db { 'mydb': + user => 'myuser', + password => 'mypass', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} +mysql::db { "mydb_${fqdn}": + user => 'myuser', + password => 'mypass', + dbname => 'mydb', + host => $::fqdn, + grant => ['SELECT', 'UPDATE'], + tag => $domain, +} diff --git a/environments/production/modules/mysql/examples/mysql_grant.pp b/environments/production/modules/mysql/examples/mysql_grant.pp new file mode 100644 index 0000000..20fe78d --- /dev/null +++ b/environments/production/modules/mysql/examples/mysql_grant.pp @@ -0,0 +1,5 @@ +mysql_grant{'test1@localhost/redmine.*': + user => 'test1@localhost', + table => 'redmine.*', + privileges => ['UPDATE'], +} diff --git a/environments/production/modules/mysql/examples/mysql_plugin.pp b/environments/production/modules/mysql/examples/mysql_plugin.pp new file mode 100644 index 0000000..d802759 --- /dev/null +++ b/environments/production/modules/mysql/examples/mysql_plugin.pp @@ -0,0 +1,23 @@ +class { 'mysql::server': + root_password => 'password' +} + +$validate_password_soname = $::osfamily ? { + windows => 'validate_password.dll', + default => 'validate_password.so' +} + +mysql::plugin { 'validate_password': + ensure => present, + soname => $validate_password_soname, +} + +$auth_socket_soname = $::osfamily ? { + windows => 'auth_socket.dll', + default => 'auth_socket.so' +} + +mysql::plugin { 'auth_socket': + ensure => present, + soname => $auth_socket_soname, +} diff --git a/environments/production/modules/mysql/examples/mysql_user.pp b/environments/production/modules/mysql/examples/mysql_user.pp new file mode 100644 index 0000000..c471868 --- /dev/null +++ b/environments/production/modules/mysql/examples/mysql_user.pp @@ -0,0 +1,32 @@ +$mysql_root_pw = 'password' + +class { 'mysql::server': + root_password => 'password', +} + +mysql_user{ 'redmine@localhost': + ensure => present, + password_hash => mysql_password('redmine'), + require => Class['mysql::server'], +} + +mysql_user{ 'dan@localhost': + ensure => present, + password_hash => mysql_password('blah') +} + +mysql_user{ 'dan@%': + ensure => present, + password_hash => mysql_password('blah'), +} + +mysql_user{ 'socketplugin@%': + ensure => present, + plugin => 'unix_socket', +} + +mysql_user{ 'socketplugin@%': + ensure => present, + password_hash => mysql_password('blah'), + plugin => 'mysql_native_password', +} diff --git a/environments/production/modules/mysql/examples/perl.pp b/environments/production/modules/mysql/examples/perl.pp new file mode 100644 index 0000000..1bbd3ab --- /dev/null +++ b/environments/production/modules/mysql/examples/perl.pp @@ -0,0 +1 @@ +include mysql::bindings::perl diff --git a/environments/production/modules/mysql/examples/python.pp b/environments/production/modules/mysql/examples/python.pp new file mode 100644 index 0000000..49ddddb --- /dev/null +++ b/environments/production/modules/mysql/examples/python.pp @@ -0,0 +1 @@ +class { 'mysql::bindings::python':} diff --git a/environments/production/modules/mysql/examples/ruby.pp b/environments/production/modules/mysql/examples/ruby.pp new file mode 100644 index 0000000..6717256 --- /dev/null +++ b/environments/production/modules/mysql/examples/ruby.pp @@ -0,0 +1 @@ +include mysql::bindings::ruby diff --git a/environments/production/modules/mysql/examples/server.pp b/environments/production/modules/mysql/examples/server.pp new file mode 100644 index 0000000..8afdd00 --- /dev/null +++ b/environments/production/modules/mysql/examples/server.pp @@ -0,0 +1,3 @@ +class { 'mysql::server': + root_password => 'password', +} diff --git a/environments/production/modules/mysql/examples/server/account_security.pp b/environments/production/modules/mysql/examples/server/account_security.pp new file mode 100644 index 0000000..cb59f4f --- /dev/null +++ b/environments/production/modules/mysql/examples/server/account_security.pp @@ -0,0 +1,4 @@ +class { 'mysql::server': + root_password => 'password', +} +class { 'mysql::server::account_security': } diff --git a/environments/production/modules/mysql/examples/server/config.pp b/environments/production/modules/mysql/examples/server/config.pp new file mode 100644 index 0000000..2cde11b --- /dev/null +++ b/environments/production/modules/mysql/examples/server/config.pp @@ -0,0 +1,3 @@ +mysql::server::config { 'testfile': + +} diff --git a/environments/production/modules/mysql/lib/facter/mysql_server_id.rb b/environments/production/modules/mysql/lib/facter/mysql_server_id.rb new file mode 100644 index 0000000..3cb39e5 --- /dev/null +++ b/environments/production/modules/mysql/lib/facter/mysql_server_id.rb @@ -0,0 +1,9 @@ +def get_mysql_id + Facter.value(:macaddress).split(':')[2..-1].inject(0) { |total,value| (total << 6) + value.hex } +end + +Facter.add("mysql_server_id") do + setcode do + get_mysql_id rescue nil + end +end diff --git a/environments/production/modules/mysql/lib/facter/mysql_version.rb b/environments/production/modules/mysql/lib/facter/mysql_version.rb new file mode 100644 index 0000000..62fba61 --- /dev/null +++ b/environments/production/modules/mysql/lib/facter/mysql_version.rb @@ -0,0 +1,8 @@ +Facter.add("mysql_version") do + setcode do + mysql_ver = Facter::Util::Resolution.exec('mysql --version') + if mysql_ver + mysql_ver.match(/\d+\.\d+\.\d+/)[0] + end + end +end diff --git a/environments/production/modules/mysql/lib/facter/mysqld_version.rb b/environments/production/modules/mysql/lib/facter/mysqld_version.rb new file mode 100644 index 0000000..c3711cc --- /dev/null +++ b/environments/production/modules/mysql/lib/facter/mysqld_version.rb @@ -0,0 +1,5 @@ +Facter.add("mysqld_version") do + setcode do + Facter::Util::Resolution.exec('mysqld -V 2>/dev/null') + end +end diff --git a/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb new file mode 100644 index 0000000..aca9c7a --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_deepmerge.rb @@ -0,0 +1,58 @@ +module Puppet::Parser::Functions + newfunction(:mysql_deepmerge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Recursively merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = mysql_deepmerge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + + When there is a duplicate key that is a hash, they are recursively merged. + When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), + the rightmost style will win. + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("mysql_deepmerge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + result = Hash.new + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + # If the argument was not a hash, skip it. + unless arg.is_a?(Hash) + raise Puppet::ParseError, "mysql_deepmerge: unexpected argument type #{arg.class}, only expects hash arguments" + end + + # Now we have to traverse our hash assigning our non-hash values + # to the matching keys in our result while following our hash values + # and repeating the process. + overlay( result, arg ) + end + return( result ) + end +end + +def has_normalized!(hash, key) + return true if hash.has_key?( key ) + return false unless key.match(/-|_/) + other_key = key.include?('-') ? key.gsub( '-', '_' ) : key.gsub( '_', '-' ) + return false unless hash.has_key?( other_key ) + hash[key] = hash.delete( other_key ) + return true; +end + +def overlay( hash1, hash2 ) + hash2.each do |key, value| + if(has_normalized!( hash1, key ) and value.is_a?(Hash) and hash1[key].is_a?(Hash)) + overlay( hash1[key], value ) + else + hash1[key] = value + end + end +end diff --git a/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb new file mode 100644 index 0000000..5d0ef55 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_dirname.rb @@ -0,0 +1,15 @@ +module Puppet::Parser::Functions + newfunction(:mysql_dirname, :type => :rvalue, :doc => <<-EOS + Returns the dirname of a path. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "mysql_dirname(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size < 1 + + path = arguments[0] + return File.dirname(path) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_password.rb b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_password.rb new file mode 100644 index 0000000..74d7fa8 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_password.rb @@ -0,0 +1,17 @@ +# hash a string as mysql's "PASSWORD()" function would do it +require 'digest/sha1' + +module Puppet::Parser::Functions + newfunction(:mysql_password, :type => :rvalue, :doc => <<-EOS + Returns the mysql password hash from the clear text password. + EOS + ) do |args| + + raise(Puppet::ParseError, 'mysql_password(): Wrong number of arguments ' + + "given (#{args.size} for 1)") if args.size != 1 + + return '' if args[0].empty? + return args[0] if args[0] =~ /\*[A-F0-9]{40}$/ + '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(args[0])).upcase + end +end diff --git a/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb new file mode 100644 index 0000000..8e850d0 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/parser/functions/mysql_strip_hash.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:mysql_strip_hash, :type => :rvalue, :arity => 1, :doc => <<-EOS +TEMPORARY FUNCTION: EXPIRES 2014-03-10 +When given a hash this function strips out all blank entries. +EOS + ) do |args| + + hash = args[0] + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'mysql_strip_hash(): Requires hash to work with') + end + + # Filter out all the top level blanks. + hash.reject{|k,v| v == ''}.each do |k,v| + if v.is_a?(Hash) + v.reject!{|ki,vi| vi == '' } + end + end + + end +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql.rb new file mode 100644 index 0000000..181d788 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql.rb @@ -0,0 +1,115 @@ +class Puppet::Provider::Mysql < Puppet::Provider + + # Without initvars commands won't work. + initvars + + # Make sure we find mysql commands on CentOS and FreeBSD + ENV['PATH']=ENV['PATH'] + ':/usr/libexec:/usr/local/libexec:/usr/local/bin' + + commands :mysql => 'mysql' + commands :mysqld => 'mysqld' + commands :mysqladmin => 'mysqladmin' + + # Optional defaults file + def self.defaults_file + if File.file?("#{Facter.value(:root_home)}/.my.cnf") + "--defaults-extra-file=#{Facter.value(:root_home)}/.my.cnf" + else + nil + end + end + + def self.mysqld_type + # find the mysql "dialect" like mariadb / mysql etc. + mysqld_version_string.scan(/mariadb/i) { return "mariadb" } + mysqld_version_string.scan(/\s\(percona/i) { return "percona" } + return "mysql" + end + + def mysqld_type + self.class.mysqld_type + end + + def self.mysqld_version_string + # As the possibility of the mysqld being remote we need to allow the version string to be overridden, this can be done by facter.value as seen below. In the case that it has not been set and the facter value is nil we use the mysql -v command to ensure we report the correct version of mysql for later use cases. + @mysqld_version_string ||= Facter.value(:mysqld_version) || mysqld('-V') + end + + def mysqld_version_string + self.class.mysqld_version_string + end + + def self.mysqld_version + # note: be prepared for '5.7.6-rc-log' etc results + # versioncmp detects 5.7.6-log to be newer then 5.7.6 + # this is why we need the trimming. + mysqld_version_string.scan(/\d+\.\d+\.\d+/).first unless mysqld_version_string.nil? + end + + def mysqld_version + self.class.mysqld_version + end + + def defaults_file + self.class.defaults_file + end + + def self.users + mysql([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"].compact).split("\n") + end + + # Optional parameter to run a statement on the MySQL system database. + def self.system_database + '--database=mysql' + end + + def system_database + self.class.system_database + end + + # Take root@localhost and munge it to 'root'@'localhost' + def self.cmd_user(user) + "'#{user.sub('@', "'@'")}'" + end + + # Take root.* and return ON `root`.* + def self.cmd_table(table) + table_string = '' + + # We can't escape *.* so special case this. + if table == '*.*' + table_string << '*.*' + # Special case also for PROCEDURES + elsif table.start_with?('PROCEDURE ') + table_string << table.sub(/^PROCEDURE (.*)(\..*)/, 'PROCEDURE `\1`\2') + else + table_string << table.sub(/^(.*)(\..*)/, '`\1`\2') + end + table_string + end + + def self.cmd_privs(privileges) + if privileges.include?('ALL') + return 'ALL PRIVILEGES' + else + priv_string = '' + privileges.each do |priv| + priv_string << "#{priv}, " + end + end + # Remove trailing , from the last element. + priv_string.sub(/, $/, '') + end + + # Take in potential options and build up a query string with them. + def self.cmd_options(options) + option_string = '' + options.each do |opt| + if opt == 'GRANT' + option_string << ' WITH GRANT OPTION' + end + end + option_string + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb new file mode 100644 index 0000000..4587c18 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql_database/mysql.rb @@ -0,0 +1,68 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_database).provide(:mysql, :parent => Puppet::Provider::Mysql) do + desc 'Manages MySQL databases.' + + commands :mysql => 'mysql' + + def self.instances + mysql([defaults_file, '-NBe', 'show databases'].compact).split("\n").collect do |name| + attributes = {} + mysql([defaults_file, '-NBe', "show variables like '%_database'", name].compact).split("\n").each do |line| + k,v = line.split(/\s/) + attributes[k] = v + end + new(:name => name, + :ensure => :present, + :charset => attributes['character_set_database'], + :collate => attributes['collation_database'] + ) + end + end + + # We iterate over each mysql_database entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + databases = instances + resources.keys.each do |database| + if provider = databases.find { |db| db.name == database } + resources[database].provider = provider + end + end + end + + def create + mysql([defaults_file, '-NBe', "create database if not exists `#{@resource[:name]}` character set `#{@resource[:charset]}` collate `#{@resource[:collate]}`"].compact) + + @property_hash[:ensure] = :present + @property_hash[:charset] = @resource[:charset] + @property_hash[:collate] = @resource[:collate] + + exists? ? (return true) : (return false) + end + + def destroy + mysql([defaults_file, '-NBe', "drop database if exists `#{@resource[:name]}`"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + mk_resource_methods + + def charset=(value) + mysql([defaults_file, '-NBe', "alter database `#{resource[:name]}` CHARACTER SET #{value}"].compact) + @property_hash[:charset] = value + charset == value ? (return true) : (return false) + end + + def collate=(value) + mysql([defaults_file, '-NBe', "alter database `#{resource[:name]}` COLLATE #{value}"].compact) + @property_hash[:collate] = value + collate == value ? (return true) : (return false) + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb new file mode 100644 index 0000000..620231e --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql_datadir/mysql.rb @@ -0,0 +1,78 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_datadir).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'manage data directories for mysql instances' + + initvars + + # Make sure we find mysqld on CentOS and mysql_install_db on Gentoo + ENV['PATH']=ENV['PATH'] + ':/usr/libexec:/usr/share/mysql/scripts:/opt/rh/mysql55/root/usr/bin:/opt/rh/mysql55/root/usr/libexec' + + commands :mysqld => 'mysqld' + commands :mysql_install_db => 'mysql_install_db' + + def create + name = @resource[:name] + insecure = @resource.value(:insecure) || true + defaults_extra_file = @resource.value(:defaults_extra_file) + user = @resource.value(:user) || "mysql" + basedir = @resource.value(:basedir) + datadir = @resource.value(:datadir) || @resource[:name] + log_error = @resource.value(:log_error) || "/var/tmp/mysqld_initialize.log" + + unless defaults_extra_file.nil? + if File.exist?(defaults_extra_file) + defaults_extra_file="--defaults-extra-file=#{defaults_extra_file}" + else + raise ArgumentError, "Defaults-extra-file #{defaults_extra_file} is missing" + end + end + + if insecure == true + initialize="--initialize-insecure" + else + initialize="--initialize" + end + + opts = [ defaults_extra_file ] + %w(basedir datadir user).each do |opt| + val = eval(opt) + opts<<"--#{opt}=#{val}" unless val.nil? + end + + if mysqld_version.nil? + debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(" ")}") + mysql_install_db(opts.compact) + else + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + opts<<"--log-error=#{log_error}" + opts<<"#{initialize}" + debug("Initializing MySQL data directory >= 5.7.6 with mysqld: #{opts.compact.join(" ")}") + mysqld(opts.compact) + else + debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(" ")}") + mysql_install_db(opts.compact) + end + end + + exists? + end + + def destroy + name = @resource[:name] + raise ArgumentError, "ERROR: Resource can not be removed" + end + + def exists? + datadir = @resource[:datadir] + (File.directory?("#{datadir}/mysql")) && (Dir.entries("#{datadir}/mysql") - %w{ . .. }).any? + end + + ## + ## MySQL datadir properties + ## + + # Generates method for all properties of the property_hash + mk_resource_methods + +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb new file mode 100644 index 0000000..40623af --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql_grant/mysql.rb @@ -0,0 +1,176 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_grant).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'Set grants for users in MySQL.' + + def self.instances + instances = [] + users.select{ |user| user =~ /.+@/ }.collect do |user| + user_string = self.cmd_user(user) + query = "SHOW GRANTS FOR #{user_string};" + begin + grants = mysql([defaults_file, "-NBe", query].compact) + rescue Puppet::ExecutionFailure => e + # Silently ignore users with no grants. Can happen e.g. if user is + # defined with fqdn and server is run with skip-name-resolve. Example: + # Default root user created by mysql_install_db on a host with fqdn + # of myhost.mydomain.my: root@myhost.mydomain.my, when MySQL is started + # with --skip-name-resolve. + if e.inspect =~ /There is no such grant defined for user/ + next + else + raise Puppet::Error, "#mysql had an error -> #{e.inspect}" + end + end + # Once we have the list of grants generate entries for each. + grants.each_line do |grant| + # Match the munges we do in the type. + munged_grant = grant.delete("'").delete("`").delete('"') + # Matching: GRANT (SELECT, UPDATE) PRIVILEGES ON (*.*) TO ('root')@('127.0.0.1') (WITH GRANT OPTION) + if match = munged_grant.match(/^GRANT\s(.+)\sON\s(.+)\sTO\s(.*)@(.*?)(\s.*)?$/) + privileges, table, user, host, rest = match.captures + table.gsub!('\\\\', '\\') + + # split on ',' if it is not a non-'('-containing string followed by a + # closing parenthesis ')'-char - e.g. only split comma separated elements not in + # parentheses + stripped_privileges = privileges.strip.split(/\s*,\s*(?![^(]*\))/).map do |priv| + # split and sort the column_privileges in the parentheses and rejoin + if priv.include?('(') + type, col=priv.strip.split(/\s+|\b/,2) + type.upcase + " (" + col.slice(1...-1).strip.split(/\s*,\s*/).sort.join(', ') + ")" + else + # Once we split privileges up on the , we need to make sure we + # shortern ALL PRIVILEGES to just all. + priv == 'ALL PRIVILEGES' ? 'ALL' : priv.strip + end + end + # Same here, but to remove OPTION leaving just GRANT. + if rest.match(/WITH\sGRANT\sOPTION/) + options = ['GRANT'] + else + options = ['NONE'] + end + # fix double backslash that MySQL prints, so resources match + table.gsub!("\\\\", "\\") + # We need to return an array of instances so capture these + instances << new( + :name => "#{user}@#{host}/#{table}", + :ensure => :present, + :privileges => stripped_privileges.sort, + :table => table, + :user => "#{user}@#{host}", + :options => options + ) + end + end + end + return instances + end + + def self.prefetch(resources) + users = instances + resources.keys.each do |name| + if provider = users.find { |user| user.name == name } + resources[name].provider = provider + end + end + end + + def grant(user, table, privileges, options) + user_string = self.class.cmd_user(user) + priv_string = self.class.cmd_privs(privileges) + table_string = self.class.cmd_table(table) + query = "GRANT #{priv_string}" + query << " ON #{table_string}" + query << " TO #{user_string}" + query << self.class.cmd_options(options) unless options.nil? + mysql([defaults_file, system_database, '-e', query].compact) + end + + def create + grant(@resource[:user], @resource[:table], @resource[:privileges], @resource[:options]) + + @property_hash[:ensure] = :present + @property_hash[:table] = @resource[:table] + @property_hash[:user] = @resource[:user] + @property_hash[:options] = @resource[:options] if @resource[:options] + @property_hash[:privileges] = @resource[:privileges] + + exists? ? (return true) : (return false) + end + + def revoke(user, table, revoke_privileges = ['ALL']) + user_string = self.class.cmd_user(user) + table_string = self.class.cmd_table(table) + priv_string = self.class.cmd_privs(revoke_privileges) + # revoke grant option needs to be a extra query, because + # "REVOKE ALL PRIVILEGES, GRANT OPTION [..]" is only valid mysql syntax + # if no ON clause is used. + # It hast to be executed before "REVOKE ALL [..]" since a GRANT has to + # exist to be executed successfully + if revoke_privileges.include? 'ALL' + query = "REVOKE GRANT OPTION ON #{table_string} FROM #{user_string}" + mysql([defaults_file, system_database, '-e', query].compact) + end + query = "REVOKE #{priv_string} ON #{table_string} FROM #{user_string}" + mysql([defaults_file, system_database, '-e', query].compact) + end + + def destroy + # if the user was dropped, it'll have been removed from the user hash + # as the grants are alraedy removed by the DROP statement + if self.class.users.include? @property_hash[:user] + revoke(@property_hash[:user], @property_hash[:table]) + end + @property_hash.clear + + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + def flush + @property_hash.clear + mysql([defaults_file, '-NBe', 'FLUSH PRIVILEGES'].compact) + end + + mk_resource_methods + + def diff_privileges(privileges_old, privileges_new) + diff = {:revoke => Array.new, :grant => Array.new} + if privileges_old.include? 'ALL' + diff[:revoke] = privileges_old + diff[:grant] = privileges_new + elsif privileges_new.include? 'ALL' + diff[:grant] = privileges_new + else + diff[:revoke] = privileges_old - privileges_new + diff[:grant] = privileges_new - privileges_old + end + return diff + end + + def privileges=(privileges) + diff = diff_privileges(@property_hash[:privileges], privileges) + if not diff[:revoke].empty? + revoke(@property_hash[:user], @property_hash[:table], diff[:revoke]) + end + if not diff[:grant].empty? + grant(@property_hash[:user], @property_hash[:table], diff[:grant], @property_hash[:options]) + end + @property_hash[:privileges] = privileges + self.privileges + end + + def options=(options) + revoke(@property_hash[:user], @property_hash[:table]) + grant(@property_hash[:user], @property_hash[:table], @property_hash[:privileges], options) + @property_hash[:options] = options + + self.options + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb new file mode 100644 index 0000000..2cb640b --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql_plugin/mysql.rb @@ -0,0 +1,53 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_plugin).provide(:mysql, :parent => Puppet::Provider::Mysql) do + desc 'Manages MySQL plugins.' + + commands :mysql => 'mysql' + + def self.instances + mysql([defaults_file, '-NBe', 'show plugins'].compact).split("\n").collect do |line| + name, status, type, library, license = line.split(/\t/) + new(:name => name, + :ensure => :present, + :soname => library + ) + end + end + + # We iterate over each mysql_plugin entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + plugins = instances + resources.keys.each do |plugin| + if provider = plugins.find { |pl| pl.name == plugin } + resources[plugin].provider = provider + end + end + end + + def create + # Use plugin_name.so as soname if it's not specified. This won't work on windows as + # there it should be plugin_name.dll + @resource[:soname].nil? ? (soname=@resource[:name] + '.so') : (soname=@resource[:soname]) + mysql([defaults_file, '-NBe', "install plugin #{@resource[:name]} soname '#{soname}'"].compact) + + @property_hash[:ensure] = :present + @property_hash[:soname] = @resource[:soname] + + exists? ? (return true) : (return false) + end + + def destroy + mysql([defaults_file, '-NBe', "uninstall plugin #{@resource[:name]}"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + mk_resource_methods + +end diff --git a/environments/production/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb b/environments/production/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb new file mode 100644 index 0000000..e5200a1 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/provider/mysql_user/mysql.rb @@ -0,0 +1,197 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) +Puppet::Type.type(:mysql_user).provide(:mysql, :parent => Puppet::Provider::Mysql) do + + desc 'manage users for a mysql database.' + commands :mysql => 'mysql' + + # Build a property_hash containing all the discovered information about MySQL + # users. + def self.instances + users = mysql([defaults_file, '-NBe', + "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"].compact).split("\n") + # To reduce the number of calls to MySQL we collect all the properties in + # one big swoop. + users.collect do |name| + if mysqld_version.nil? + ## Default ... + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + else + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + else + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + end + end + @max_user_connections, @max_connections_per_hour, @max_queries_per_hour, + @max_updates_per_hour, ssl_type, ssl_cipher, x509_issuer, x509_subject, + @password, @plugin = mysql([defaults_file, "-NBe", query].compact).split(/\s/) + @tls_options = parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) + + new(:name => name, + :ensure => :present, + :password_hash => @password, + :plugin => @plugin, + :max_user_connections => @max_user_connections, + :max_connections_per_hour => @max_connections_per_hour, + :max_queries_per_hour => @max_queries_per_hour, + :max_updates_per_hour => @max_updates_per_hour, + :tls_options => @tls_options, + ) + end + end + + # We iterate over each mysql_user entry in the catalog and compare it against + # the contents of the property_hash generated by self.instances + def self.prefetch(resources) + users = instances + resources.keys.each do |name| + if provider = users.find { |user| user.name == name } + resources[name].provider = provider + end + end + end + + def create + merged_name = @resource[:name].sub('@', "'@'") + password_hash = @resource.value(:password_hash) + plugin = @resource.value(:plugin) + max_user_connections = @resource.value(:max_user_connections) || 0 + max_connections_per_hour = @resource.value(:max_connections_per_hour) || 0 + max_queries_per_hour = @resource.value(:max_queries_per_hour) || 0 + max_updates_per_hour = @resource.value(:max_updates_per_hour) || 0 + tls_options = @resource.value(:tls_options) || ['NONE'] + + # Use CREATE USER to be compatible with NO_AUTO_CREATE_USER sql_mode + # This is also required if you want to specify a authentication plugin + if !plugin.nil? + if plugin == 'sha256_password' and !password_hash.nil? + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}' AS '#{password_hash}'"].compact) + else + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}'"].compact) + end + @property_hash[:ensure] = :present + @property_hash[:plugin] = plugin + else + mysql([defaults_file, system_database, '-e', "CREATE USER '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}'"].compact) + @property_hash[:ensure] = :present + @property_hash[:password_hash] = password_hash + end + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}"].compact) + @property_hash[:max_user_connections] = max_user_connections + @property_hash[:max_connections_per_hour] = max_connections_per_hour + @property_hash[:max_queries_per_hour] = max_queries_per_hour + @property_hash[:max_updates_per_hour] = max_updates_per_hour + + merged_tls_options = tls_options.join(' AND ') + if (((mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0) or + (mysqld_type == 'mariadb' and Puppet::Util::Package.versioncmp(mysqld_version, '10.2.0') >= 0)) + mysql([defaults_file, system_database, '-e', "ALTER USER '#{merged_name}' REQUIRE #{merged_tls_options}"].compact) + else + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO '#{merged_name}' REQUIRE #{merged_tls_options}"].compact) + end + @property_hash[:tls_options] = tls_options + + exists? ? (return true) : (return false) + end + + def destroy + merged_name = @resource[:name].sub('@', "'@'") + mysql([defaults_file, system_database, '-e', "DROP USER '#{merged_name}'"].compact) + + @property_hash.clear + exists? ? (return false) : (return true) + end + + def exists? + @property_hash[:ensure] == :present || false + end + + ## + ## MySQL user properties + ## + + # Generates method for all properties of the property_hash + mk_resource_methods + + def password_hash=(string) + merged_name = self.class.cmd_user(@resource[:name]) + + # We have a fact for the mysql version ... + if mysqld_version.nil? + # default ... if mysqld_version does not work + mysql([defaults_file, system_database, '-e', "SET PASSWORD FOR #{merged_name} = '#{string}'"].compact) + else + # Version >= 5.7.6 (many password related changes) + if (mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0 + if string.match(/^\*/) + mysql([defaults_file, system_database, '-e', "ALTER USER #{merged_name} IDENTIFIED WITH mysql_native_password AS '#{string}'"].compact) + else + raise ArgumentError, "Only mysql_native_password (*ABCD...XXX) hashes are supported" + end + else + # older versions + mysql([defaults_file, system_database, '-e', "SET PASSWORD FOR #{merged_name} = '#{string}'"].compact) + end + end + + password_hash == string ? (return true) : (return false) + end + + def max_user_connections=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_USER_CONNECTIONS #{int}"].compact).chomp + + max_user_connections == int ? (return true) : (return false) + end + + def max_connections_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_CONNECTIONS_PER_HOUR #{int}"].compact).chomp + + max_connections_per_hour == int ? (return true) : (return false) + end + + def max_queries_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_QUERIES_PER_HOUR #{int}"].compact).chomp + + max_queries_per_hour == int ? (return true) : (return false) + end + + def max_updates_per_hour=(int) + merged_name = self.class.cmd_user(@resource[:name]) + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} WITH MAX_UPDATES_PER_HOUR #{int}"].compact).chomp + + max_updates_per_hour == int ? (return true) : (return false) + end + + def tls_options=(array) + merged_name = self.class.cmd_user(@resource[:name]) + merged_tls_options = array.join(' AND ') + if (((mysqld_type == "mysql" or mysqld_type == "percona") and Puppet::Util::Package.versioncmp(mysqld_version, '5.7.6') >= 0) or + (mysqld_type == 'mariadb' and Puppet::Util::Package.versioncmp(mysqld_version, '10.2.0') >= 0)) + mysql([defaults_file, system_database, '-e', "ALTER USER #{merged_name} REQUIRE #{merged_tls_options}"].compact) + else + mysql([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO #{merged_name} REQUIRE #{merged_tls_options}"].compact) + end + + tls_options == array ? (return true) : (return false) + end + + def self.parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) + if ssl_type == 'ANY' + return ['SSL'] + elsif ssl_type == 'X509' + return ['X509'] + elsif ssl_type == 'SPECIFIED' + options = [] + options << "CIPHER #{ssl_cipher}" if not ssl_cipher.nil? and not ssl_cipher.empty? + options << "ISSUER #{x509_issuer}" if not x509_issuer.nil? and not x509_issuer.empty? + options << "SUBJECT #{x509_subject}" if not x509_subject.nil? and not x509_subject.empty? + return options + else + return ['NONE'] + end + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/type/mysql_database.rb b/environments/production/modules/mysql/lib/puppet/type/mysql_database.rb new file mode 100644 index 0000000..1f94d5f --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/type/mysql_database.rb @@ -0,0 +1,25 @@ +Puppet::Type.newtype(:mysql_database) do + @doc = 'Manage MySQL databases.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:class) { 'mysql::server' } + + newparam(:name, :namevar => true) do + desc 'The name of the MySQL database to manage.' + end + + newproperty(:charset) do + desc 'The CHARACTER SET setting for the database' + defaultto :utf8 + newvalue(/^\S+$/) + end + + newproperty(:collate) do + desc 'The COLLATE setting for the database' + defaultto :utf8_general_ci + newvalue(/^\S+$/) + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/type/mysql_datadir.rb b/environments/production/modules/mysql/lib/puppet/type/mysql_datadir.rb new file mode 100644 index 0000000..367767d --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/type/mysql_datadir.rb @@ -0,0 +1,35 @@ +Puppet::Type.newtype(:mysql_datadir) do + @doc = 'Manage MySQL datadirs with mysql_install_db OR mysqld (5.7.6 and above).' + + ensurable + + autorequire(:package) { 'mysql-server' } + + newparam(:datadir, :namevar => true) do + desc "The datadir name" + end + + newparam(:basedir) do + desc 'The basedir name, default /usr.' + newvalues(/^\//) + end + + newparam(:user) do + desc 'The user for the directory default mysql (name, not uid).' + end + + newparam(:defaults_extra_file) do + desc "MySQL defaults-extra-file with absolute path (*.cnf)." + newvalues(/^\/.*\.cnf$/) + end + + newparam(:insecure, :boolean => true) do + desc "Insecure initialization (needed for 5.7.6++)." + end + + newparam(:log_error) do + desc "The path to the mysqld error log file (used with the --log-error option)" + newvalues(/^\//) + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/type/mysql_grant.rb b/environments/production/modules/mysql/lib/puppet/type/mysql_grant.rb new file mode 100644 index 0000000..7385f93 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/type/mysql_grant.rb @@ -0,0 +1,103 @@ +# This has to be a separate type to enable collecting +Puppet::Type.newtype(:mysql_grant) do + @doc = "Manage a MySQL user's rights." + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:mysql_user) { self[:user] } + + def initialize(*args) + super + # Forcibly munge any privilege with 'ALL' in the array to exist of just + # 'ALL'. This can't be done in the munge in the property as that iterates + # over the array and there's no way to replace the entire array before it's + # returned to the provider. + if self[:ensure] == :present and Array(self[:privileges]).count > 1 and self[:privileges].to_s.include?('ALL') + self[:privileges] = 'ALL' + end + # Sort the privileges array in order to ensure the comparision in the provider + # self.instances method match. Otherwise this causes it to keep resetting the + # privileges. + self[:privileges] = Array(self[:privileges]).map{ |priv| + # split and sort the column_privileges in the parentheses and rejoin + if priv.include?('(') + type, col=priv.strip.split(/\s+|\b/,2) + type.upcase + " (" + col.slice(1...-1).strip.split(/\s*,\s*/).sort.join(', ') + ")" + else + priv.strip.upcase + end + }.uniq.reject{|k| k == 'GRANT' or k == 'GRANT OPTION'}.sort! + end + + validate do + fail('privileges parameter is required.') if self[:ensure] == :present and self[:privileges].nil? + fail('table parameter is required.') if self[:ensure] == :present and self[:table].nil? + fail('user parameter is required.') if self[:ensure] == :present and self[:user].nil? + fail('name must match user and table parameters') if self[:name] != "#{self[:user]}/#{self[:table]}" + end + + newparam(:name, :namevar => true) do + desc 'Name to describe the grant.' + + munge do |value| + value.delete("'") + end + end + + newproperty(:privileges, :array_matching => :all) do + desc 'Privileges for user' + end + + newproperty(:table) do + desc 'Table to apply privileges to.' + + munge do |value| + value.delete("`") + end + + newvalues(/.*\..*/,/@/) + end + + newproperty(:user) do + desc 'User to operate on.' + validate do |value| + # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html + # If at least one special char is used, string must be quoted + + # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 + if matches = /^(['`"])((?!\1).)*\1@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[2] + host_part = matches[3] + elsif matches = /^([0-9a-zA-Z$_]*)@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + elsif matches = /^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + else + raise(ArgumentError, "Invalid database user #{value}") + end + + mysql_version = Facter.value(:mysql_version) + unless mysql_version.nil? + if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 and user_part.size > 16 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 16 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 and user_part.size > 32 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 32 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 and user_part.size > 80 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 80 characters') + end + end + end + + munge do |value| + matches = /^((['`"]?).*\2)@(.+)$/.match(value) + "#{matches[1]}@#{matches[3].downcase}" + end + end + + newproperty(:options, :array_matching => :all) do + desc 'Options to grant.' + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/type/mysql_plugin.rb b/environments/production/modules/mysql/lib/puppet/type/mysql_plugin.rb new file mode 100644 index 0000000..e827920 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/type/mysql_plugin.rb @@ -0,0 +1,17 @@ +Puppet::Type.newtype(:mysql_plugin) do + @doc = 'Manage MySQL plugins.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + + newparam(:name, :namevar => true) do + desc 'The name of the MySQL plugin to manage.' + end + + newproperty(:soname) do + desc 'The name of the library' + newvalue(/^\w+\.\w+$/) + end + +end diff --git a/environments/production/modules/mysql/lib/puppet/type/mysql_user.rb b/environments/production/modules/mysql/lib/puppet/type/mysql_user.rb new file mode 100644 index 0000000..831a531 --- /dev/null +++ b/environments/production/modules/mysql/lib/puppet/type/mysql_user.rb @@ -0,0 +1,105 @@ +# This has to be a separate type to enable collecting +Puppet::Type.newtype(:mysql_user) do + @doc = 'Manage a MySQL user. This includes management of users password as well as privileges.' + + ensurable + + autorequire(:file) { '/root/.my.cnf' } + autorequire(:class) { 'mysql::server' } + + newparam(:name, :namevar => true) do + desc "The name of the user. This uses the 'username@hostname' or username@hostname." + validate do |value| + # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html + # If at least one special char is used, string must be quoted + + # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 + if matches = /^(['`"])((?:(?!\1).)*)\1@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[2] + host_part = matches[3] + elsif matches = /^([0-9a-zA-Z$_]*)@([\w%\.:\-\/]+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + elsif matches = /^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$/.match(value) + user_part = matches[1] + host_part = matches[2] + else + raise(ArgumentError, "Invalid database user #{value}") + end + + mysql_version = Facter.value(:mysql_version) + unless mysql_version.nil? + if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8') < 0 and user_part.size > 16 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 16 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') < 0 and user_part.size > 32 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 32 characters') + elsif Puppet::Util::Package.versioncmp(mysql_version, '10.0.0') > 0 and user_part.size > 80 + raise(ArgumentError, 'MySQL usernames are limited to a maximum of 80 characters') + end + end + end + + munge do |value| + matches = /^((['`"]?).*\2)@(.+)$/.match(value) + "#{matches[1]}@#{matches[3].downcase}" + end + end + + newproperty(:password_hash) do + desc 'The password hash of the user. Use mysql_password() for creating such a hash.' + newvalue(/\w*/) + end + + newproperty(:plugin) do + desc 'The authentication plugin of the user.' + newvalue(/\w+/) + end + + newproperty(:max_user_connections) do + desc "Max concurrent connections for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_connections_per_hour) do + desc "Max connections per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_queries_per_hour) do + desc "Max queries per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:max_updates_per_hour) do + desc "Max updates per hour for the user. 0 means no (or global) limit." + newvalue(/\d+/) + end + + newproperty(:tls_options, :array_matching => :all) do + desc "Options to that set the TLS-related REQUIRE attributes for the user." + validate do |value| + value = [value] if not value.is_a?(Array) + if value.include? 'NONE' or value.include? 'SSL' or value.include? 'X509' + if value.length > 1 + raise(ArgumentError, "REQUIRE tls options NONE, SSL and X509 cannot be used with other options, you may only use one of them.") + end + else + value.each do |opt| + if not o = opt.match(/^(CIPHER|ISSUER|SUBJECT)/i) + raise(ArgumentError, "Invalid tls option #{o}") + end + end + end + end + def insync?(is) + # The current value may be nil and we don't + # want to call sort on it so make sure we have arrays + if is.is_a?(Array) and @should.is_a?(Array) + is.sort == @should.sort + else + is == @should + end + end + end + +end diff --git a/environments/production/modules/mysql/manifests/backup/mysqlbackup.pp b/environments/production/modules/mysql/manifests/backup/mysqlbackup.pp new file mode 100644 index 0000000..ae70c1b --- /dev/null +++ b/environments/production/modules/mysql/manifests/backup/mysqlbackup.pp @@ -0,0 +1,106 @@ +# See README.me for usage. +class mysql::backup::mysqlbackup ( + $backupuser = '', + $backuppassword = '', + $maxallowedpacket = '1M', + $backupdir = '', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = true, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + mysql_user { "${backupuser}@localhost": + ensure => $ensure, + password_hash => mysql_password($backuppassword), + require => Class['mysql::server::root_password'], + } + + package { 'meb': + ensure => $ensure, + } + + # http://dev.mysql.com/doc/mysql-enterprise-backup/3.11/en/mysqlbackup.privileges.html + mysql_grant { "${backupuser}@localhost/*.*": + ensure => $ensure, + user => "${backupuser}@localhost", + table => '*.*', + privileges => [ 'RELOAD', 'SUPER', 'REPLICATION CLIENT' ], + require => Mysql_user["${backupuser}@localhost"], + } + + mysql_grant { "${backupuser}@localhost/mysql.backup_progress": + ensure => $ensure, + user => "${backupuser}@localhost", + table => 'mysql.backup_progress', + privileges => [ 'CREATE', 'INSERT', 'DROP', 'UPDATE' ], + require => Mysql_user["${backupuser}@localhost"], + } + + mysql_grant { "${backupuser}@localhost/mysql.backup_history": + ensure => $ensure, + user => "${backupuser}@localhost", + table => 'mysql.backup_history', + privileges => [ 'CREATE', 'INSERT', 'SELECT', 'DROP', 'UPDATE' ], + require => Mysql_user["${backupuser}@localhost"], + } + + cron { 'mysqlbackup-weekly': + ensure => $ensure, + command => 'mysqlbackup backup', + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '0', + require => Package['meb'], + } + + cron { 'mysqlbackup-daily': + ensure => $ensure, + command => 'mysqlbackup --incremental backup', + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '1-6', + require => Package['meb'], + } + + $default_options = { + 'mysqlbackup' => { + 'backup-dir' => $backupdir, + 'with-timestamp' => true, + 'incremental_base' => 'history:last_backup', + 'incremental_backup_dir' => $backupdir, + 'user' => $backupuser, + 'password' => $backuppassword, + } + } + $options = mysql_deepmerge($default_options, $mysql::server::override_options) + + file { 'mysqlbackup-config-file': + path => '/etc/mysql/conf.d/meb.cnf', + content => template('mysql/meb.cnf.erb'), + mode => '0600', + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + +} diff --git a/environments/production/modules/mysql/manifests/backup/mysqldump.pp b/environments/production/modules/mysql/manifests/backup/mysqldump.pp new file mode 100644 index 0000000..3ff3c9b --- /dev/null +++ b/environments/production/modules/mysql/manifests/backup/mysqldump.pp @@ -0,0 +1,73 @@ +# See README.me for usage. +class mysql::backup::mysqldump ( + $backupuser = '', + $backuppassword = '', + $backupdir = '', + $maxallowedpacket = '1M', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = false, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + ensure_packages(['bzip2']) + + mysql_user { "${backupuser}@localhost": + ensure => $ensure, + password_hash => mysql_password($backuppassword), + require => Class['mysql::server::root_password'], + } + + if $include_triggers { + $privs = [ 'SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER' ] + } else { + $privs = [ 'SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS' ] + } + + mysql_grant { "${backupuser}@localhost/*.*": + ensure => $ensure, + user => "${backupuser}@localhost", + table => '*.*', + privileges => $privs, + require => Mysql_user["${backupuser}@localhost"], + } + + cron { 'mysql-backup': + ensure => $ensure, + command => '/usr/local/sbin/mysqlbackup.sh', + user => 'root', + hour => $time[0], + minute => $time[1], + require => [File['mysqlbackup.sh'], Package['bzip2']], + } + + file { 'mysqlbackup.sh': + ensure => $ensure, + path => '/usr/local/sbin/mysqlbackup.sh', + mode => '0700', + owner => 'root', + group => $mysql::params::root_group, + content => template('mysql/mysqlbackup.sh.erb'), + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + +} diff --git a/environments/production/modules/mysql/manifests/backup/xtrabackup.pp b/environments/production/modules/mysql/manifests/backup/xtrabackup.pp new file mode 100644 index 0000000..185b29f --- /dev/null +++ b/environments/production/modules/mysql/manifests/backup/xtrabackup.pp @@ -0,0 +1,67 @@ +# See README.me for usage. +class mysql::backup::xtrabackup ( + $xtrabackup_package_name = $mysql::params::xtrabackup_package_name, + $backupuser = '', + $backuppassword = '', + $backupdir = '', + $maxallowedpacket = '1M', + $backupmethod = 'mysqldump', + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = $mysql::params::root_group, + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_triggers = true, + $include_routines = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', +) inherits mysql::params { + + package{ $xtrabackup_package_name: + ensure => $ensure, + } + + cron { 'xtrabackup-weekly': + ensure => $ensure, + command => "/usr/local/sbin/xtrabackup.sh ${backupdir}", + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '0', + require => Package[$xtrabackup_package_name], + } + + cron { 'xtrabackup-daily': + ensure => $ensure, + command => "/usr/local/sbin/xtrabackup.sh --incremental ${backupdir}", + user => 'root', + hour => $time[0], + minute => $time[1], + weekday => '1-6', + require => Package[$xtrabackup_package_name], + } + + file { 'mysqlbackupdir': + ensure => 'directory', + path => $backupdir, + mode => $backupdirmode, + owner => $backupdirowner, + group => $backupdirgroup, + } + + file { 'xtrabackup.sh': + ensure => $ensure, + path => '/usr/local/sbin/xtrabackup.sh', + mode => '0700', + owner => 'root', + group => $mysql::params::root_group, + content => template('mysql/xtrabackup.sh.erb'), + } +} diff --git a/environments/production/modules/mysql/manifests/bindings.pp b/environments/production/modules/mysql/manifests/bindings.pp new file mode 100644 index 0000000..fa7b870 --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings.pp @@ -0,0 +1,57 @@ +# See README.md. +class mysql::bindings ( + $install_options = undef, + # Boolean to determine if we should include the classes. + $java_enable = false, + $perl_enable = false, + $php_enable = false, + $python_enable = false, + $ruby_enable = false, + $client_dev = false, + $daemon_dev = false, + # Settings for the various classes. + $java_package_ensure = $mysql::params::java_package_ensure, + $java_package_name = $mysql::params::java_package_name, + $java_package_provider = $mysql::params::java_package_provider, + $perl_package_ensure = $mysql::params::perl_package_ensure, + $perl_package_name = $mysql::params::perl_package_name, + $perl_package_provider = $mysql::params::perl_package_provider, + $php_package_ensure = $mysql::params::php_package_ensure, + $php_package_name = $mysql::params::php_package_name, + $php_package_provider = $mysql::params::php_package_provider, + $python_package_ensure = $mysql::params::python_package_ensure, + $python_package_name = $mysql::params::python_package_name, + $python_package_provider = $mysql::params::python_package_provider, + $ruby_package_ensure = $mysql::params::ruby_package_ensure, + $ruby_package_name = $mysql::params::ruby_package_name, + $ruby_package_provider = $mysql::params::ruby_package_provider, + $client_dev_package_ensure = $mysql::params::client_dev_package_ensure, + $client_dev_package_name = $mysql::params::client_dev_package_name, + $client_dev_package_provider = $mysql::params::client_dev_package_provider, + $daemon_dev_package_ensure = $mysql::params::daemon_dev_package_ensure, + $daemon_dev_package_name = $mysql::params::daemon_dev_package_name, + $daemon_dev_package_provider = $mysql::params::daemon_dev_package_provider +) inherits mysql::params { + + case $::osfamily { + 'Archlinux': { + if $java_enable { fail("::mysql::bindings::java cannot be managed by puppet on ${::osfamily} as it is not in official repositories. Please disable java mysql binding.") } + if $perl_enable { include '::mysql::bindings::perl' } + if $php_enable { warning("::mysql::bindings::php does not need to be managed by puppet on ${::osfamily} as it is included in mysql package by default.") } + if $python_enable { include '::mysql::bindings::python' } + if $ruby_enable { fail("::mysql::bindings::ruby cannot be managed by puppet on ${::osfamily} as it is not in official repositories. Please disable ruby mysql binding.") } + } + + default: { + if $java_enable { include '::mysql::bindings::java' } + if $perl_enable { include '::mysql::bindings::perl' } + if $php_enable { include '::mysql::bindings::php' } + if $python_enable { include '::mysql::bindings::python' } + if $ruby_enable { include '::mysql::bindings::ruby' } + } + } + + if $client_dev { include '::mysql::bindings::client_dev' } + if $daemon_dev { include '::mysql::bindings::daemon_dev' } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/client_dev.pp b/environments/production/modules/mysql/manifests/bindings/client_dev.pp new file mode 100644 index 0000000..ee3c2fe --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/client_dev.pp @@ -0,0 +1,15 @@ +# Private class +class mysql::bindings::client_dev { + + if $mysql::bindings::client_dev_package_name { + package { 'mysql-client_dev': + ensure => $mysql::bindings::client_dev_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::client_dev_package_name, + provider => $mysql::bindings::client_dev_package_provider, + } + } else { + warning("No MySQL client development package configured for ${::operatingsystem}.") + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/daemon_dev.pp b/environments/production/modules/mysql/manifests/bindings/daemon_dev.pp new file mode 100644 index 0000000..0515358 --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/daemon_dev.pp @@ -0,0 +1,15 @@ +# Private class +class mysql::bindings::daemon_dev { + + if $mysql::bindings::daemon_dev_package_name { + package { 'mysql-daemon_dev': + ensure => $mysql::bindings::daemon_dev_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::daemon_dev_package_name, + provider => $mysql::bindings::daemon_dev_package_provider, + } + } else { + warning("No MySQL daemon development package configured for ${::operatingsystem}.") + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/java.pp b/environments/production/modules/mysql/manifests/bindings/java.pp new file mode 100644 index 0000000..db93ac2 --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/java.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::java { + + package { 'mysql-connector-java': + ensure => $mysql::bindings::java_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::java_package_name, + provider => $mysql::bindings::java_package_provider, + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/perl.pp b/environments/production/modules/mysql/manifests/bindings/perl.pp new file mode 100644 index 0000000..99d429c --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/perl.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::perl { + + package{ 'perl_mysql': + ensure => $mysql::bindings::perl_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::perl_package_name, + provider => $mysql::bindings::perl_package_provider, + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/php.pp b/environments/production/modules/mysql/manifests/bindings/php.pp new file mode 100644 index 0000000..9af7069 --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/php.pp @@ -0,0 +1,11 @@ +# Private class: See README.md +class mysql::bindings::php { + + package { 'php-mysql': + ensure => $mysql::bindings::php_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::php_package_name, + provider => $mysql::bindings::php_package_provider, + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/python.pp b/environments/production/modules/mysql/manifests/bindings/python.pp new file mode 100644 index 0000000..a44c8fa --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/python.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::python { + + package { 'python-mysqldb': + ensure => $mysql::bindings::python_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::python_package_name, + provider => $mysql::bindings::python_package_provider, + } + +} diff --git a/environments/production/modules/mysql/manifests/bindings/ruby.pp b/environments/production/modules/mysql/manifests/bindings/ruby.pp new file mode 100644 index 0000000..d431efe --- /dev/null +++ b/environments/production/modules/mysql/manifests/bindings/ruby.pp @@ -0,0 +1,11 @@ +# Private class +class mysql::bindings::ruby { + + package{ 'ruby_mysql': + ensure => $mysql::bindings::ruby_package_ensure, + install_options => $mysql::bindings::install_options, + name => $mysql::bindings::ruby_package_name, + provider => $mysql::bindings::ruby_package_provider, + } + +} diff --git a/environments/production/modules/mysql/manifests/client.pp b/environments/production/modules/mysql/manifests/client.pp new file mode 100644 index 0000000..3d5d706 --- /dev/null +++ b/environments/production/modules/mysql/manifests/client.pp @@ -0,0 +1,29 @@ +# +class mysql::client ( + $bindings_enable = $mysql::params::bindings_enable, + $install_options = undef, + $package_ensure = $mysql::params::client_package_ensure, + $package_manage = $mysql::params::client_package_manage, + $package_name = $mysql::params::client_package_name, +) inherits mysql::params { + + include '::mysql::client::install' + + if $bindings_enable { + class { 'mysql::bindings': + java_enable => true, + perl_enable => true, + php_enable => true, + python_enable => true, + ruby_enable => true, + } + } + + + # Anchor pattern workaround to avoid resources of mysql::client::install to + # "float off" outside mysql::client + anchor { 'mysql::client::start': } -> + Class['mysql::client::install'] -> + anchor { 'mysql::client::end': } + +} diff --git a/environments/production/modules/mysql/manifests/client/install.pp b/environments/production/modules/mysql/manifests/client/install.pp new file mode 100644 index 0000000..26e5ec2 --- /dev/null +++ b/environments/production/modules/mysql/manifests/client/install.pp @@ -0,0 +1,14 @@ +# See README.md. +class mysql::client::install { + + if $mysql::client::package_manage { + + package { 'mysql_client': + ensure => $mysql::client::package_ensure, + install_options => $mysql::client::install_options, + name => $mysql::client::package_name, + } + + } + +} diff --git a/environments/production/modules/mysql/manifests/db.pp b/environments/production/modules/mysql/manifests/db.pp new file mode 100644 index 0000000..f5b2221 --- /dev/null +++ b/environments/production/modules/mysql/manifests/db.pp @@ -0,0 +1,72 @@ +# See README.md for details. +define mysql::db ( + $user, + $password, + $dbname = $name, + $charset = 'utf8', + $collate = 'utf8_general_ci', + $host = 'localhost', + $grant = 'ALL', + $sql = undef, + $enforce_sql = false, + $ensure = 'present', + $import_timeout = 300, + $import_cat_cmd = 'cat', +) { + #input validation + validate_re($ensure, '^(present|absent)$', + "${ensure} is not supported for ensure. Allowed values are 'present' and 'absent'.") + $table = "${dbname}.*" + + if !(is_array($sql) or is_string($sql)) { + fail('$sql must be either a string or an array.') + } + + $sql_inputs = join([$sql], ' ') + + include '::mysql::client' + + $db_resource = { + ensure => $ensure, + charset => $charset, + collate => $collate, + provider => 'mysql', + require => [ Class['mysql::client'] ], + } + ensure_resource('mysql_database', $dbname, $db_resource) + + $user_resource = { + ensure => $ensure, + password_hash => mysql_password($password), + provider => 'mysql', + } + ensure_resource('mysql_user', "${user}@${host}", $user_resource) + + if $ensure == 'present' { + mysql_grant { "${user}@${host}/${table}": + privileges => $grant, + provider => 'mysql', + user => "${user}@${host}", + table => $table, + require => [ + Mysql_database[$dbname], + Mysql_user["${user}@${host}"], + ], + } + + $refresh = ! $enforce_sql + + if $sql { + exec{ "${dbname}-import": + command => "${import_cat_cmd} ${sql_inputs} | mysql ${dbname}", + logoutput => true, + environment => "HOME=${::root_home}", + refreshonly => $refresh, + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin', + require => Mysql_grant["${user}@${host}/${table}"], + subscribe => Mysql_database[$dbname], + timeout => $import_timeout, + } + } + } +} diff --git a/environments/production/modules/mysql/manifests/params.pp b/environments/production/modules/mysql/manifests/params.pp new file mode 100644 index 0000000..8f65b8f --- /dev/null +++ b/environments/production/modules/mysql/manifests/params.pp @@ -0,0 +1,450 @@ +# Private class: See README.md. +class mysql::params { + + $manage_config_file = true + $purge_conf_dir = false + $restart = false + $root_password = 'UNSET' + $install_secret_file = '/.mysql_secret' + $server_package_ensure = 'present' + $server_package_manage = true + $server_service_manage = true + $server_service_enabled = true + $client_package_ensure = 'present' + $client_package_manage = true + $create_root_user = true + $create_root_my_cnf = true + # mysql::bindings + $bindings_enable = false + $java_package_ensure = 'present' + $java_package_provider = undef + $perl_package_ensure = 'present' + $perl_package_provider = undef + $php_package_ensure = 'present' + $php_package_provider = undef + $python_package_ensure = 'present' + $python_package_provider = undef + $ruby_package_ensure = 'present' + $ruby_package_provider = undef + $client_dev_package_ensure = 'present' + $client_dev_package_provider = undef + $daemon_dev_package_ensure = 'present' + $daemon_dev_package_provider = undef + $xtrabackup_package_name = 'percona-xtrabackup' + + + case $::osfamily { + 'RedHat': { + case $::operatingsystem { + 'Fedora': { + if versioncmp($::operatingsystemrelease, '19') >= 0 or $::operatingsystemrelease == 'Rawhide' { + $provider = 'mariadb' + } else { + $provider = 'mysql' + } + } + /^(RedHat|CentOS|Scientific|OracleLinux)$/: { + if versioncmp($::operatingsystemmajrelease, '7') >= 0 { + $provider = 'mariadb' + } else { + $provider = 'mysql' + } + } + default: { + $provider = 'mysql' + } + } + + if $provider == 'mariadb' { + $client_package_name = 'mariadb' + $server_package_name = 'mariadb-server' + $server_service_name = 'mariadb' + $log_error = '/var/log/mariadb/mariadb.log' + $config_file = '/etc/my.cnf.d/server.cnf' + # mariadb package by default has !includedir set in my.cnf to /etc/my.cnf.d + $includedir = undef + $pidfile = '/var/run/mariadb/mariadb.pid' + $daemon_dev_package_name = 'mariadb-devel' + } else { + $client_package_name = 'mysql' + $server_package_name = 'mysql-server' + $server_service_name = 'mysqld' + $log_error = '/var/log/mysqld.log' + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $pidfile = '/var/run/mysqld/mysqld.pid' + $daemon_dev_package_name = 'mysql-devel' + } + + $basedir = '/usr' + $datadir = '/var/lib/mysql' + $root_group = 'root' + $mysql_group = 'mysql' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-MySQL' + $php_package_name = 'php-mysql' + $python_package_name = 'MySQL-python' + $ruby_package_name = 'ruby-mysql' + $client_dev_package_name = undef + } + + 'Suse': { + case $::operatingsystem { + 'OpenSuSE': { + if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb' + # First service start fails if this is set. Runs fine without + # it being set, in any case. Leaving it as-is for the mysql. + $basedir = undef + } else { + $client_package_name = 'mysql-community-server-client' + $server_package_name = 'mysql-community-server' + $basedir = '/usr' + } + } + 'SLES','SLED': { + if versioncmp($::operatingsystemrelease, '12') >= 0 { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb' + $basedir = undef + } else { + $client_package_name = 'mysql-client' + $server_package_name = 'mysql' + $basedir = '/usr' + } + } + default: { + fail("Unsupported platform: puppetlabs-${module_name} currently doesn't support ${::operatingsystem}") + } + } + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $datadir = '/var/lib/mysql' + $log_error = $::operatingsystem ? { + /OpenSuSE/ => '/var/log/mysql/mysqld.log', + /(SLES|SLED)/ => '/var/log/mysqld.log', + } + $pidfile = $::operatingsystem ? { + /OpenSuSE/ => '/var/run/mysql/mysqld.pid', + /(SLES|SLED)/ => '/var/lib/mysql/mysqld.pid', + } + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysql' + + if $::operatingsystem =~ /(SLES|SLED)/ { + if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { + $socket = '/run/mysql/mysql.sock' + } else { + $socket = '/var/lib/mysql/mysql.sock' + } + } else { + $socket = '/var/run/mysql/mysql.sock' + } + + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-mysql' + $php_package_name = 'apache2-mod_php53' + $python_package_name = 'python-mysql' + $ruby_package_name = $::operatingsystem ? { + /OpenSuSE/ => 'rubygem-mysql', + /(SLES|SLED)/ => 'ruby-mysql', + } + $client_dev_package_name = 'libmysqlclient-devel' + $daemon_dev_package_name = 'mysql-devel' + } + + 'Debian': { + $client_package_name = 'mysql-client' + $server_package_name = 'mysql-server' + + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $includedir = '/etc/mysql/conf.d' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysql/error.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'adm' + $server_service_name = 'mysql' + $socket = '/var/run/mysqld/mysqld.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'libmysql-java' + $perl_package_name = 'libdbd-mysql-perl' + $php_package_name = $::lsbdistcodename ? { + 'xenial' => 'php-mysql', + default => 'php5-mysql', + } + $python_package_name = 'python-mysqldb' + $ruby_package_name = $::lsbdistcodename ? { + 'trusty' => 'ruby-mysql', + 'jessie' => 'ruby-mysql', + 'xenial' => 'ruby-mysql', + default => 'libmysql-ruby', + } + $client_dev_package_name = 'libmysqlclient-dev' + $daemon_dev_package_name = 'libmysqld-dev' + } + + 'Archlinux': { + $client_package_name = 'mariadb-clients' + $server_package_name = 'mariadb' + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysqld' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-dbd-mysql' + $php_package_name = undef + $python_package_name = 'mysql-python' + $ruby_package_name = 'mysql-ruby' + } + + 'Gentoo': { + $client_package_name = 'virtual/mysql' + $server_package_name = 'virtual/mysql' + $basedir = '/usr' + $config_file = '/etc/mysql/my.cnf' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysql/mysqld.err' + $pidfile = '/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysql' + $socket = '/run/mysqld/mysqld.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'dev-java/jdbc-mysql' + $perl_package_name = 'dev-perl/DBD-mysql' + $php_package_name = undef + $python_package_name = 'dev-python/mysql-python' + $ruby_package_name = 'dev-ruby/mysql-ruby' + } + + 'FreeBSD': { + $client_package_name = 'databases/mysql56-client' + $server_package_name = 'databases/mysql56-server' + $basedir = '/usr/local' + $config_file = '/usr/local/etc/my.cnf' + $includedir = '/usr/local/etc/my.cnf.d' + $datadir = '/var/db/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysql.pid' + $root_group = 'wheel' + $mysql_group = 'mysql' + $server_service_name = 'mysql-server' + $socket = '/var/db/mysql/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'databases/mysql-connector-java' + $perl_package_name = 'p5-DBD-mysql' + $php_package_name = 'php5-mysql' + $python_package_name = 'databases/py-MySQLdb' + $ruby_package_name = 'databases/ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + 'OpenBSD': { + $client_package_name = 'mariadb-client' + $server_package_name = 'mariadb-server' + $basedir = '/usr/local' + $config_file = '/etc/my.cnf' + $includedir = undef + $datadir = '/var/mysql' + $log_error = "/var/mysql/${::hostname}.err" + $pidfile = '/var/mysql/mysql.pid' + $root_group = 'wheel' + $mysql_group = '_mysql' + $server_service_name = 'mysqld' + $socket = '/var/run/mysql/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = undef + $perl_package_name = 'p5-DBD-mysql' + $php_package_name = 'php-mysql' + $python_package_name = 'py-mysql' + $ruby_package_name = 'ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + 'Solaris': { + $client_package_name = 'database/mysql-55/client' + $server_package_name = 'database/mysql-55' + $basedir = undef + $config_file = '/etc/mysql/5.5/my.cnf' + $datadir = '/var/mysql/5.5/data' + $log_error = "/var/mysql/5.5/data/${::hostname}.err" + $pidfile = "/var/mysql/5.5/data/${::hostname}.pid" + $root_group = 'bin' + $server_service_name = 'application/database/mysql:version_55' + $socket = '/tmp/mysql.sock' + $ssl_ca = undef + $ssl_cert = undef + $ssl_key = undef + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = undef + $perl_package_name = undef + $php_package_name = 'web/php-53/extension/php-mysql' + $python_package_name = 'library/python/python-mysql' + $ruby_package_name = undef + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + default: { + case $::operatingsystem { + 'Amazon': { + $client_package_name = 'mysql' + $server_package_name = 'mysql-server' + $basedir = '/usr' + $config_file = '/etc/my.cnf' + $includedir = '/etc/my.cnf.d' + $datadir = '/var/lib/mysql' + $log_error = '/var/log/mysqld.log' + $pidfile = '/var/run/mysqld/mysqld.pid' + $root_group = 'root' + $mysql_group = 'mysql' + $server_service_name = 'mysqld' + $socket = '/var/lib/mysql/mysql.sock' + $ssl_ca = '/etc/mysql/cacert.pem' + $ssl_cert = '/etc/mysql/server-cert.pem' + $ssl_key = '/etc/mysql/server-key.pem' + $tmpdir = '/tmp' + # mysql::bindings + $java_package_name = 'mysql-connector-java' + $perl_package_name = 'perl-DBD-MySQL' + $php_package_name = 'php-mysql' + $python_package_name = 'MySQL-python' + $ruby_package_name = 'ruby-mysql' + # The libraries installed by these packages are included in client and server packages, no installation required. + $client_dev_package_name = undef + $daemon_dev_package_name = undef + } + + default: { + fail("Unsupported platform: puppetlabs-${module_name} currently doesn't support ${::osfamily} or ${::operatingsystem}") + } + } + } + } + + case $::operatingsystem { + 'Ubuntu': { + if versioncmp($::operatingsystemmajrelease, '14.10') > 0 { + $server_service_provider = 'systemd' + } else { + $server_service_provider = 'upstart' + } + } + default: { + $server_service_provider = undef + } + } + + $default_options = { + 'client' => { + 'port' => '3306', + 'socket' => $mysql::params::socket, + }, + 'mysqld_safe' => { + 'nice' => '0', + 'log-error' => $mysql::params::log_error, + 'socket' => $mysql::params::socket, + }, + 'mysqld-5.0' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.1' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.5' => { + 'myisam-recover' => 'BACKUP', + }, + 'mysqld-5.6' => { + 'myisam-recover-options' => 'BACKUP', + }, + 'mysqld-5.7' => { + 'myisam-recover-options' => 'BACKUP', + }, + 'mysqld' => { + 'basedir' => $mysql::params::basedir, + 'bind-address' => '127.0.0.1', + 'datadir' => $mysql::params::datadir, + 'expire_logs_days' => '10', + 'key_buffer_size' => '16M', + 'log-error' => $mysql::params::log_error, + 'max_allowed_packet' => '16M', + 'max_binlog_size' => '100M', + 'max_connections' => '151', + 'pid-file' => $mysql::params::pidfile, + 'port' => '3306', + 'query_cache_limit' => '1M', + 'query_cache_size' => '16M', + 'skip-external-locking' => true, + 'socket' => $mysql::params::socket, + 'ssl' => false, + 'ssl-ca' => $mysql::params::ssl_ca, + 'ssl-cert' => $mysql::params::ssl_cert, + 'ssl-key' => $mysql::params::ssl_key, + 'ssl-disable' => false, + 'thread_cache_size' => '8', + 'thread_stack' => '256K', + 'tmpdir' => $mysql::params::tmpdir, + 'user' => 'mysql', + }, + 'mysqldump' => { + 'max_allowed_packet' => '16M', + 'quick' => true, + 'quote-names' => true, + }, + 'isamchk' => { + 'key_buffer_size' => '16M', + }, + } + + ## Additional graceful failures + if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '4' and $::operatingsystem != 'Amazon' { + fail("Unsupported platform: puppetlabs-${module_name} only supports RedHat 5.0 and beyond") + } +} diff --git a/environments/production/modules/mysql/manifests/server.pp b/environments/production/modules/mysql/manifests/server.pp new file mode 100644 index 0000000..e910c21 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server.pp @@ -0,0 +1,89 @@ +# Class: mysql::server: See README.md for documentation. +class mysql::server ( + $config_file = $mysql::params::config_file, + $includedir = $mysql::params::includedir, + $install_options = undef, + $install_secret_file = $mysql::params::install_secret_file, + $manage_config_file = $mysql::params::manage_config_file, + $override_options = {}, + $package_ensure = $mysql::params::server_package_ensure, + $package_manage = $mysql::params::server_package_manage, + $package_name = $mysql::params::server_package_name, + $purge_conf_dir = $mysql::params::purge_conf_dir, + $remove_default_accounts = false, + $restart = $mysql::params::restart, + $root_group = $mysql::params::root_group, + $mysql_group = $mysql::params::mysql_group, + $root_password = $mysql::params::root_password, + $service_enabled = $mysql::params::server_service_enabled, + $service_manage = $mysql::params::server_service_manage, + $service_name = $mysql::params::server_service_name, + $service_provider = $mysql::params::server_service_provider, + $create_root_user = $mysql::params::create_root_user, + $create_root_my_cnf = $mysql::params::create_root_my_cnf, + $users = {}, + $grants = {}, + $databases = {}, + + # Deprecated parameters + $enabled = undef, + $manage_service = undef, + $old_root_password = undef +) inherits mysql::params { + + # Deprecated parameters. + if $enabled { + crit('This parameter has been renamed to service_enabled.') + $real_service_enabled = $enabled + } else { + $real_service_enabled = $service_enabled + } + if $manage_service { + crit('This parameter has been renamed to service_manage.') + $real_service_manage = $manage_service + } else { + $real_service_manage = $service_manage + } + if $old_root_password { + warning('old_root_password is no longer used and will be removed in a future release') + } + + # Create a merged together set of options. Rightmost hashes win over left. + $options = mysql_deepmerge($mysql::params::default_options, $override_options) + + Class['mysql::server::root_password'] -> Mysql::Db <| |> + + include '::mysql::server::config' + include '::mysql::server::install' + include '::mysql::server::binarylog' + include '::mysql::server::installdb' + include '::mysql::server::service' + include '::mysql::server::root_password' + include '::mysql::server::providers' + + if $remove_default_accounts { + class { '::mysql::server::account_security': + require => Anchor['mysql::server::end'], + } + } + + anchor { 'mysql::server::start': } + anchor { 'mysql::server::end': } + + if $restart { + Class['mysql::server::config'] ~> + Class['mysql::server::service'] + } + + Anchor['mysql::server::start'] -> + Class['mysql::server::config'] -> + Class['mysql::server::install'] -> + Class['mysql::server::binarylog'] -> + Class['mysql::server::installdb'] -> + Class['mysql::server::service'] -> + Class['mysql::server::root_password'] -> + Class['mysql::server::providers'] -> + Anchor['mysql::server::end'] + + +} diff --git a/environments/production/modules/mysql/manifests/server/account_security.pp b/environments/production/modules/mysql/manifests/server/account_security.pp new file mode 100644 index 0000000..3fcdd61 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/account_security.pp @@ -0,0 +1,39 @@ +# See README.md. +class mysql::server::account_security { + mysql_user { + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + '@%']: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + if ($::fqdn != 'localhost.localdomain') { + mysql_user { + [ 'root@localhost.localdomain', + '@localhost.localdomain']: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + if ($::fqdn and $::fqdn != 'localhost') { + mysql_user { + [ "root@${::fqdn}", + "@${::fqdn}"]: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + if ($::fqdn != $::hostname) { + if ($::hostname != 'localhost') { + mysql_user { ["root@${::hostname}", "@${::hostname}"]: + ensure => 'absent', + require => Anchor['mysql::server::end'], + } + } + } + mysql_database { 'test': + ensure => 'absent', + require => Anchor['mysql::server::end'], + } +} diff --git a/environments/production/modules/mysql/manifests/server/backup.pp b/environments/production/modules/mysql/manifests/server/backup.pp new file mode 100644 index 0000000..d31a880 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/backup.pp @@ -0,0 +1,55 @@ +# See README.me for usage. +class mysql::server::backup ( + $backupuser = undef, + $backuppassword = undef, + $backupdir = undef, + $backupdirmode = '0700', + $backupdirowner = 'root', + $backupdirgroup = 'root', + $backupcompress = true, + $backuprotate = 30, + $ignore_events = true, + $delete_before_dump = false, + $backupdatabases = [], + $file_per_database = false, + $include_routines = false, + $include_triggers = false, + $ensure = 'present', + $time = ['23', '5'], + $prescript = false, + $postscript = false, + $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', + $provider = 'mysqldump', + $maxallowedpacket = '1M', +) { + + if $prescript and $provider =~ /(mysqldump|mysqlbackup)/ { + warning("The \$prescript option is not currently implemented for the ${provider} backup provider.") + } + + create_resources('class', { + "mysql::backup::${provider}" => { + 'backupuser' => $backupuser, + 'backuppassword' => $backuppassword, + 'backupdir' => $backupdir, + 'backupdirmode' => $backupdirmode, + 'backupdirowner' => $backupdirowner, + 'backupdirgroup' => $backupdirgroup, + 'backupcompress' => $backupcompress, + 'backuprotate' => $backuprotate, + 'ignore_events' => $ignore_events, + 'delete_before_dump' => $delete_before_dump, + 'backupdatabases' => $backupdatabases, + 'file_per_database' => $file_per_database, + 'include_routines' => $include_routines, + 'include_triggers' => $include_triggers, + 'ensure' => $ensure, + 'time' => $time, + 'prescript' => $prescript, + 'postscript' => $postscript, + 'execpath' => $execpath, + 'maxallowedpacket' => $maxallowedpacket, + } + }) + +} diff --git a/environments/production/modules/mysql/manifests/server/binarylog.pp b/environments/production/modules/mysql/manifests/server/binarylog.pp new file mode 100644 index 0000000..459915d --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/binarylog.pp @@ -0,0 +1,22 @@ +# Binary log configuration requires the mysql user to be present. This must be done after package install +class mysql::server::binarylog { + + $options = $mysql::server::options + $includedir = $mysql::server::includedir + + $logbin = pick($options['mysqld']['log-bin'], $options['mysqld']['log_bin'], false) + + if $logbin { + $logbindir = mysql_dirname($logbin) + + #Stop puppet from managing directory if just a filename/prefix is specified + if $logbindir != '.' { + file { $logbindir: + ensure => directory, + mode => '0755', + owner => $options['mysqld']['user'], + group => $options['mysqld']['user'], + } + } + } +} diff --git a/environments/production/modules/mysql/manifests/server/config.pp b/environments/production/modules/mysql/manifests/server/config.pp new file mode 100644 index 0000000..efb4940 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/config.pp @@ -0,0 +1,56 @@ +# See README.me for options. +class mysql::server::config { + + $options = $mysql::server::options + $includedir = $mysql::server::includedir + + File { + owner => 'root', + group => $mysql::server::root_group, + mode => '0400', + } + + if $includedir and $includedir != '' { + file { $includedir: + ensure => directory, + mode => '0755', + recurse => $mysql::server::purge_conf_dir, + purge => $mysql::server::purge_conf_dir, + } + + # on some systems this is /etc/my.cnf.d, while Debian has /etc/mysql/conf.d and FreeBSD something in /usr/local. For the latter systems, + # managing this basedir is also required, to have it available before the package is installed. + $includeparentdir = mysql_dirname($includedir) + if $includeparentdir != '/' and $includeparentdir != '/etc' { + file { $includeparentdir: + ensure => directory, + mode => '0755', + } + } + } + + if $mysql::server::manage_config_file { + file { 'mysql-config-file': + path => $mysql::server::config_file, + content => template('mysql/my.cnf.erb'), + mode => '0644', + selinux_ignore_defaults => true, + } + + # on mariadb systems, $includedir is not defined, but /etc/my.cnf.d has + # to be managed to place the server.cnf there + $configparentdir = mysql_dirname($mysql::server::config_file) + if $configparentdir != '/' and $configparentdir != '/etc' and $configparentdir != $includedir and $configparentdir != mysql_dirname($includedir) { + file { $configparentdir: + ensure => directory, + mode => '0755', + } + } + } + + if $options['mysqld']['ssl-disable'] { + notify {'ssl-disable': + message =>'Disabling SSL is evil! You should never ever do this except if you are forced to use a mysql version compiled without SSL support' + } + } +} diff --git a/environments/production/modules/mysql/manifests/server/install.pp b/environments/production/modules/mysql/manifests/server/install.pp new file mode 100644 index 0000000..3b9601d --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/install.pp @@ -0,0 +1,13 @@ +# +class mysql::server::install { + + if $mysql::server::package_manage { + + package { 'mysql-server': + ensure => $mysql::server::package_ensure, + install_options => $mysql::server::install_options, + name => $mysql::server::package_name, + } + } + +} diff --git a/environments/production/modules/mysql/manifests/server/installdb.pp b/environments/production/modules/mysql/manifests/server/installdb.pp new file mode 100644 index 0000000..5b419f2 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/installdb.pp @@ -0,0 +1,46 @@ +# +class mysql::server::installdb { + $options = $mysql::server::options + + if $mysql::server::package_manage { + + # Build the initial databases. + $mysqluser = $mysql::server::options['mysqld']['user'] + $datadir = $mysql::server::options['mysqld']['datadir'] + $basedir = $mysql::server::options['mysqld']['basedir'] + $config_file = $mysql::server::config_file + $log_error = $mysql::server::options['mysqld']['log-error'] + + if $mysql::server::manage_config_file and $config_file != $mysql::params::config_file { + $_config_file=$config_file + } else { + $_config_file=undef + } + + if $options['mysqld']['log-error'] { + file { $options['mysqld']['log-error']: + ensure => present, + owner => $mysqluser, + group => $::mysql::server::mysql_group, + mode => 'u+rw', + before => Mysql_datadir[ $datadir ], + } + } + + mysql_datadir { $datadir: + ensure => 'present', + datadir => $datadir, + basedir => $basedir, + user => $mysqluser, + log_error => $log_error, + defaults_extra_file => $_config_file, + } + + if $mysql::server::restart { + Mysql_datadir[$datadir] { + notify => Class['mysql::server::service'], + } + } + } + +} diff --git a/environments/production/modules/mysql/manifests/server/monitor.pp b/environments/production/modules/mysql/manifests/server/monitor.pp new file mode 100644 index 0000000..6b18609 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/monitor.pp @@ -0,0 +1,24 @@ +#This is a helper class to add a monitoring user to the database +class mysql::server::monitor ( + $mysql_monitor_username = '', + $mysql_monitor_password = '', + $mysql_monitor_hostname = '' +) { + + Anchor['mysql::server::end'] -> Class['mysql::server::monitor'] + + mysql_user { "${mysql_monitor_username}@${mysql_monitor_hostname}": + ensure => present, + password_hash => mysql_password($mysql_monitor_password), + require => Class['mysql::server::service'], + } + + mysql_grant { "${mysql_monitor_username}@${mysql_monitor_hostname}/*.*": + ensure => present, + user => "${mysql_monitor_username}@${mysql_monitor_hostname}", + table => '*.*', + privileges => [ 'PROCESS', 'SUPER' ], + require => Mysql_user["${mysql_monitor_username}@${mysql_monitor_hostname}"], + } + +} diff --git a/environments/production/modules/mysql/manifests/server/mysqltuner.pp b/environments/production/modules/mysql/manifests/server/mysqltuner.pp new file mode 100644 index 0000000..ff1d563 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/mysqltuner.pp @@ -0,0 +1,52 @@ +# +class mysql::server::mysqltuner( + $ensure = 'present', + $version = 'v1.3.0', + $source = undef, +) { + + if $source { + $_version = $source + $_source = $source + } else { + $_version = $version + $_source = "https://github.com/major/MySQLTuner-perl/raw/${version}/mysqltuner.pl" + } + + if $ensure == 'present' { + # $::puppetversion doesn't exist in puppet 4.x so would break strict + # variables + if ! $::settings::strict_variables { + $_puppetversion = $::puppetversion + } else { + # defined only works with puppet >= 3.5.0, so don't use it unless we're + # actually using strict variables + $_puppetversion = defined('$puppetversion') ? { + true => $::puppetversion, + default => undef, + } + } + # see https://tickets.puppetlabs.com/browse/ENTERPRISE-258 + if $_puppetversion and $_puppetversion =~ /Puppet Enterprise/ and versioncmp($_puppetversion, '3.8.0') < 0 { + class { '::staging': + path => '/opt/mysql_staging', + } + } else { + class { '::staging': } + } + + staging::file { "mysqltuner-${_version}": + source => $_source, + } + file { '/usr/local/bin/mysqltuner': + ensure => $ensure, + mode => '0550', + source => "${::staging::path}/mysql/mysqltuner-${_version}", + require => Staging::File["mysqltuner-${_version}"], + } + } else { + file { '/usr/local/bin/mysqltuner': + ensure => $ensure, + } + } +} diff --git a/environments/production/modules/mysql/manifests/server/providers.pp b/environments/production/modules/mysql/manifests/server/providers.pp new file mode 100644 index 0000000..b651172 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/providers.pp @@ -0,0 +1,8 @@ +# Convenience class to call each of the three providers with the corresponding +# hashes provided in mysql::server. +# See README.md for details. +class mysql::server::providers { + create_resources('mysql_user', $mysql::server::users) + create_resources('mysql_grant', $mysql::server::grants) + create_resources('mysql_database', $mysql::server::databases) +} diff --git a/environments/production/modules/mysql/manifests/server/root_password.pp b/environments/production/modules/mysql/manifests/server/root_password.pp new file mode 100644 index 0000000..9ebc103 --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/root_password.pp @@ -0,0 +1,46 @@ +# +class mysql::server::root_password { + + $options = $mysql::server::options + $secret_file = $mysql::server::install_secret_file + + # New installations of MySQL will configure a default random password for the root user + # with an expiration. No actions can be performed until this password is changed. The + # below exec will remove this default password. If the user has supplied a root + # password it will be set further down with the mysql_user resource. + $rm_pass_cmd = join([ + "mysqladmin -u root --password=\$(grep -o '[^ ]\\+\$' ${secret_file}) password ''", + "rm -f ${secret_file}" + ], ' && ') + exec { 'remove install pass': + command => $rm_pass_cmd, + onlyif => "test -f ${secret_file}", + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + } + + # manage root password if it is set + if $mysql::server::create_root_user == true and $mysql::server::root_password != 'UNSET' { + mysql_user { 'root@localhost': + ensure => present, + password_hash => mysql_password($mysql::server::root_password), + require => Exec['remove install pass'] + } + } + + if $mysql::server::create_root_my_cnf == true and $mysql::server::root_password != 'UNSET' { + file { "${::root_home}/.my.cnf": + content => template('mysql/my.cnf.pass.erb'), + owner => 'root', + mode => '0600', + } + + # show_diff was added with puppet 3.0 + if versioncmp($::puppetversion, '3.0') >= 0 { + File["${::root_home}/.my.cnf"] { show_diff => false } + } + if $mysql::server::create_root_user == true { + Mysql_user['root@localhost'] -> File["${::root_home}/.my.cnf"] + } + } + +} diff --git a/environments/production/modules/mysql/manifests/server/service.pp b/environments/production/modules/mysql/manifests/server/service.pp new file mode 100644 index 0000000..3617b4b --- /dev/null +++ b/environments/production/modules/mysql/manifests/server/service.pp @@ -0,0 +1,58 @@ +# +class mysql::server::service { + $options = $mysql::server::options + + if $mysql::server::real_service_manage { + if $mysql::server::real_service_enabled { + $service_ensure = 'running' + } else { + $service_ensure = 'stopped' + } + } else { + $service_ensure = undef + } + + if $mysql::server::override_options and $mysql::server::override_options['mysqld'] and $mysql::server::override_options['mysqld']['user'] { + $mysqluser = $mysql::server::override_options['mysqld']['user'] + } else { + $mysqluser = $options['mysqld']['user'] + } + + if $mysql::server::real_service_manage { + service { 'mysqld': + ensure => $service_ensure, + name => $mysql::server::service_name, + enable => $mysql::server::real_service_enabled, + provider => $mysql::server::service_provider, + } + + # only establish ordering between service and package if + # we're managing the package. + if $mysql::server::package_manage { + Service['mysqld'] { + require => Package['mysql-server'], + } + } + + # only establish ordering between config file and service if + # we're managing the config file. + if $mysql::server::manage_config_file { + File['mysql-config-file'] -> Service['mysqld'] + } + + if $mysql::server::override_options and $mysql::server::override_options['mysqld'] and $mysql::server::override_options['mysqld']['socket'] { + $mysqlsocket = $mysql::server::override_options['mysqld']['socket'] + } else { + $mysqlsocket = $options['mysqld']['socket'] + } + + exec { 'wait_for_mysql_socket_to_open': + command => "test -S ${mysqlsocket}", + unless => "test -S ${mysqlsocket}", + tries => '3', + try_sleep => '10', + require => Service['mysqld'], + path => '/bin:/usr/bin', + } + } +} diff --git a/environments/production/modules/mysql/metadata.json b/environments/production/modules/mysql/metadata.json new file mode 100644 index 0000000..41c8da1 --- /dev/null +++ b/environments/production/modules/mysql/metadata.json @@ -0,0 +1,80 @@ +{ + "name": "puppetlabs-mysql", + "version": "3.10.0", + "author": "Puppet Labs", + "summary": "Installs, configures, and manages the MySQL service.", + "license": "Apache-2.0", + "source": "git://github.com/puppetlabs/puppetlabs-mysql.git", + "project_page": "http://github.com/puppetlabs/puppetlabs-mysql", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "dependencies": [ + {"name":"puppetlabs/stdlib","version_requirement":">= 3.2.0 < 5.0.0"}, + {"name":"puppet/staging","version_requirement":">= 1.0.1 < 3.0.0"} + ], + "data_provider": null, + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "11 SP1", + "12" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7", + "8" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04", + "16.04" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 3.0.0 < 5.0.0" + } + ], + "description": "MySQL module" +} diff --git a/environments/production/modules/mysql/spec/acceptance/mysql_backup_spec.rb b/environments/production/modules/mysql/spec/acceptance/mysql_backup_spec.rb new file mode 100644 index 0000000..88a90cc --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/mysql_backup_spec.rb @@ -0,0 +1,205 @@ +require 'spec_helper_acceptance' +require 'puppet' +require 'puppet/util/package' +require_relative './mysql_helper.rb' + +describe 'mysql::server::backup class' do + context 'should work with no errors' do + it 'when configuring mysql backups' do + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'mysqlbackup.sh' do + it 'should run mysqlbackup.sh with no errors' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("/usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + + it 'should dump all databases to single file' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| + expect(r.stdout).to match(/1/) + expect(r.exit_code).to be_zero + end + end + end + + context 'should create one file per database per run' do + it 'executes mysqlbackup.sh a second time' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('sleep 1') + shell('/usr/local/sbin/mysqlbackup.sh') + end + end + + it 'creates at least one backup tarball' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| + expect(r.stdout).to match(/2/) + expect(r.exit_code).to be_zero + end + end + end + end + end + + context 'with one file per database' do + context 'should work with no errors' do + it 'when configuring mysql backups' do + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + file_per_database => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'mysqlbackup.sh' do + it 'should run mysqlbackup.sh with no errors without root credentials' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + + it 'should create one file per database' do + pre_run + if ! version_is_greater_than('5.7.0') + ['backup1', 'backup2'].each do |database| + shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(/1/) + expect(r.exit_code).to be_zero + end + end + end + end + + context 'should create one file per database per run' do + it 'executes mysqlbackup.sh a second time' do + pre_run + if ! version_is_greater_than('5.7.0') + shell('sleep 1') + shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') + end + end + + it 'has one file per database per run' do + pre_run + if ! version_is_greater_than('5.7.0') + ['backup1', 'backup2'].each do |database| + shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| + expect(r.stdout).to match(/2/) + expect(r.exit_code).to be_zero + end + end + end + end + end + end + end + + context 'with triggers and routines' do + it 'when configuring mysql backups with triggers and routines' do + pre_run + pp = <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { [ + 'backup1', + 'backup2' + ]: + user => 'backup', + password => 'secret', + } + package { 'bzip2': + ensure => present, + } + class { 'mysql::server::backup': + backupuser => 'myuser', + backuppassword => 'mypassword', + backupdir => '/tmp/backups', + backupcompress => true, + file_per_database => true, + include_triggers => #{version_is_greater_than('5.1.5')}, + include_routines => true, + postscript => [ + 'rm -rf /var/tmp/mysqlbackups', + 'rm -f /var/tmp/mysqlbackups.done', + 'cp -r /tmp/backups /var/tmp/mysqlbackups', + 'touch /var/tmp/mysqlbackups.done', + ], + execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + require => Package['bzip2'], + } + EOS + apply_manifest(pp, :catch_failures => true) + end + + it 'should run mysqlbackup.sh with no errors' do + pre_run + if ! version_is_greater_than('5.7.0') + shell("/usr/local/sbin/mysqlbackup.sh") do |r| + expect(r.stderr).to eq("") + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/acceptance/mysql_db_spec.rb b/environments/production/modules/mysql/spec/acceptance/mysql_db_spec.rb new file mode 100644 index 0000000..8c57160 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/mysql_db_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper_acceptance' + +describe 'mysql::db define' do + describe 'creating a database' do + let(:pp) do + <<-EOS + class { 'mysql::server': root_password => 'password' } + mysql::db { 'spec1': + user => 'root1', + password => 'password', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show databases;'") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^spec1$/ } + end + end + + describe 'creating a database with post-sql' do + let(:pp) do + <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + file { '/tmp/spec.sql': + ensure => file, + content => 'CREATE TABLE table1 (id int);', + before => Mysql::Db['spec2'], + } + mysql::db { 'spec2': + user => 'root1', + password => 'password', + sql => '/tmp/spec.sql', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show tables;' spec2") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^table1$/ } + end + end + + describe 'creating a database with dbname parameter' do + let(:check_command) { " | grep realdb" } + let(:pp) do + <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + mysql::db { 'spec1': + user => 'root1', + password => 'password', + dbname => 'realdb', + } + EOS + end + it_behaves_like "a idempotent resource" + + describe command("mysql -e 'show databases;'") do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match /^realdb$/ } + end + end +end diff --git a/environments/production/modules/mysql/spec/acceptance/mysql_helper.rb b/environments/production/modules/mysql/spec/acceptance/mysql_helper.rb new file mode 100644 index 0000000..7a5dd57 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/mysql_helper.rb @@ -0,0 +1,8 @@ + def pre_run + apply_manifest("class { 'mysql::server': root_password => 'password' }", :catch_failures => true) + @mysql_version = (on default, 'mysql --version').output.chomp.match(/\d+\.\d+\.\d+/)[0] + end + + def version_is_greater_than(version) + return Puppet::Util::Package.versioncmp(@mysql_version, version) > 0 + end diff --git a/environments/production/modules/mysql/spec/acceptance/mysql_server_spec.rb b/environments/production/modules/mysql/spec/acceptance/mysql_server_spec.rb new file mode 100644 index 0000000..d8e2f3e --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/mysql_server_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper_acceptance' + +describe 'mysql class' do + describe 'advanced config' do + before(:all) do + @tmpdir = default.tmpdir('mysql') + end + let(:pp) do + <<-EOS + class { 'mysql::server': + config_file => '#{@tmpdir}/my.cnf', + includedir => '#{@tmpdir}/include', + manage_config_file => 'true', + override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, + package_ensure => 'present', + purge_conf_dir => 'true', + remove_default_accounts => 'true', + restart => 'true', + root_group => 'root', + root_password => 'test', + service_enabled => 'true', + service_manage => 'true', + users => { + 'someuser@localhost' => { + ensure => 'present', + max_connections_per_hour => '0', + max_queries_per_hour => '0', + max_updates_per_hour => '0', + max_user_connections => '0', + password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', + }}, + grants => { + 'someuser@localhost/somedb.*' => { + ensure => 'present', + options => ['GRANT'], + privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], + table => 'somedb.*', + user => 'someuser@localhost', + }, + }, + databases => { + 'somedb' => { + ensure => 'present', + charset => 'utf8', + }, + } + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + describe 'minimal config' do + before(:all) do + @tmpdir = default.tmpdir('mysql') + end + let(:pp) do + <<-EOS + class { 'mysql::server': + config_file => '#{@tmpdir}/my.cnf', + includedir => '#{@tmpdir}/include', + manage_config_file => 'false', + override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, + package_ensure => 'present', + purge_conf_dir => 'false', + remove_default_accounts => 'false', + restart => 'false', + root_group => 'root', + root_password => 'test', + service_enabled => 'false', + service_manage => 'false', + users => {}, + grants => {}, + databases => {}, + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + describe 'syslog configuration' do + let(:pp) do + <<-EOS + class { 'mysql::server': + override_options => { 'mysqld' => { 'log-error' => undef }, 'mysqld_safe' => { 'log-error' => false, 'syslog' => true }}, + } + EOS + end + + it_behaves_like "a idempotent resource" + end + + context 'when changing the password' do + let(:password) { 'THE NEW SECRET' } + let(:pp) { "class { 'mysql::server': root_password => '#{password}' }" } + + it 'should not display the password' do + result = apply_manifest(pp, :catch_failures => true) + # this does not actually prove anything, as show_diff in the puppet config defaults to false. + expect(result.stdout).not_to match /#{password}/ + end + + it_behaves_like "a idempotent resource" + end +end diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml new file mode 100644 index 0000000..5eebdef --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/centos-7-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-7-x64: + roles: + - agent + - default + platform: el-7-x86_64 + hypervisor: vagrant + box: puppetlabs/centos-7.2-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml new file mode 100644 index 0000000..fef6e63 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/debian-8-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + debian-8-x64: + roles: + - agent + - default + platform: debian-8-amd64 + hypervisor: vagrant + box: puppetlabs/debian-8.2-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/default.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..dba339c --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/default.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-1404-x64: + roles: + - agent + - default + platform: ubuntu-14.04-amd64 + hypervisor: vagrant + box: puppetlabs/ubuntu-14.04-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml new file mode 100644 index 0000000..a3333aa --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/centos-7.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-7-x64: + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: '["/usr/sbin/init"]' + # install various tools required to get the image up to usable levels + docker_image_commands: + - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml new file mode 100644 index 0000000..df5c319 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/debian-8.yml @@ -0,0 +1,11 @@ +HOSTS: + debian-8-x64: + platform: debian-8-amd64 + hypervisor: docker + image: debian:8 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml new file mode 100644 index 0000000..b1efa58 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/nodesets/docker/ubuntu-14.04.yml @@ -0,0 +1,12 @@ +HOSTS: + ubuntu-1404-x64: + platform: ubuntu-14.04-amd64 + hypervisor: docker + image: ubuntu:14.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + # ensure that upstart is booting correctly in the container + - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/mysql/spec/acceptance/types/mysql_database_spec.rb b/environments/production/modules/mysql/spec/acceptance/types/mysql_database_spec.rb new file mode 100644 index 0000000..b19026d --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/types/mysql_database_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper_acceptance' + +describe 'mysql_database' do + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'creating database' do + it 'should work without errors' do + pp = <<-EOS + mysql_database { 'spec_db': + ensure => present, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the database' do + shell("mysql -NBe \"SHOW DATABASES LIKE 'spec_db'\"") do |r| + expect(r.stdout).to match(/^spec_db$/) + expect(r.stderr).to be_empty + end + end + end + + describe 'charset and collate' do + it 'should create two db of different types idempotently' do + pp = <<-EOS + mysql_database { 'spec_latin1': + charset => 'latin1', + collate => 'latin1_swedish_ci', + } + mysql_database { 'spec_utf8': + charset => 'utf8', + collate => 'utf8_general_ci', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should find latin1 db' do + shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_latin1") do |r| + expect(r.stdout).to match(/^character_set_database\tlatin1\ncollation_database\tlatin1_swedish_ci$/) + expect(r.stderr).to be_empty + end + end + + it 'should find utf8 db' do + shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_utf8") do |r| + expect(r.stdout).to match(/^character_set_database\tutf8\ncollation_database\tutf8_general_ci$/) + expect(r.stderr).to be_empty + end + end + end +end diff --git a/environments/production/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb b/environments/production/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb new file mode 100644 index 0000000..ccf2b27 --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/types/mysql_grant_spec.rb @@ -0,0 +1,578 @@ +require 'spec_helper_acceptance' +require 'puppet' +require 'puppet/util/package' +require_relative '../mysql_helper.rb' + +describe 'mysql_grant' do + before(:all) do + pp = <<-EOS + class { 'mysql::server': + root_password => 'password', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe 'missing privileges for user' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test1@tester': + ensure => present, + } + mysql_grant { 'test1@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test1@tester', + require => Mysql_user['test1@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/privileges parameter is required/) + end + + it 'should not find the user' do + expect(shell("mysql -NBe \"SHOW GRANTS FOR test1@tester\"", { :acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'test1' on host 'tester'/) + end + end + + describe 'missing table for user' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'atest@tester': + ensure => present, + } + mysql_grant { 'atest@tester/test.*': + ensure => 'present', + user => 'atest@tester', + privileges => ['ALL'], + require => Mysql_user['atest@tester'], + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + + it 'should not find the user' do + expect(shell("mysql -NBe \"SHOW GRANTS FOR atest@tester\"", {:acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'atest' on host 'tester'/) + end + end + + describe 'adding privileges' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test2@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test2@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE.*TO 'test2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding privileges with special character in name' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test-2@tester': + ensure => present, + } + mysql_grant { 'test-2@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test-2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test-2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test-2'@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE.*TO 'test-2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding privileges with invalid name' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test': + ensure => 'present', + table => 'test.*', + user => 'test2@tester', + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test2@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/name must match user and table parameters/) + end + end + + describe 'adding option' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'test3@tester': + ensure => present, + } + mysql_grant { 'test3@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test3@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE'], + require => Mysql_user['test3@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test3@tester\"") do |r| + expect(r.stdout).to match(/GRANT SELECT, UPDATE ON `test`.* TO 'test3'@'tester' WITH GRANT OPTION$/) + expect(r.stderr).to be_empty + end + end + end + + describe 'adding all privileges without table' do + it 'should fail' do + pp = <<-EOS + mysql_user { 'test4@tester': + ensure => present, + } + mysql_grant { 'test4@tester/test.*': + ensure => 'present', + user => 'test4@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE', 'ALL'], + require => Mysql_user['test4@tester'], + } + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/table parameter is required./) + end + end + + describe 'adding all privileges' do + it 'should only try to apply ALL' do + pp = <<-EOS + mysql_user { 'test4@tester': + ensure => present, + } + mysql_grant { 'test4@tester/test.*': + ensure => 'present', + table => 'test.*', + user => 'test4@tester', + options => ['GRANT'], + privileges => ['SELECT', 'UPDATE', 'ALL'], + require => Mysql_user['test4@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test4@tester\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test4'@'tester' WITH GRANT OPTION/) + expect(r.stderr).to be_empty + end + end + end + + # Test combinations of user@host to ensure all cases work. + describe 'short hostname' do + it 'should apply' do + pp = <<-EOS + mysql_user { 'test@short': + ensure => present, + } + mysql_grant { 'test@short/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@short', + privileges => 'ALL', + require => Mysql_user['test@short'], + } + mysql_user { 'test@long.hostname.com': + ensure => present, + } + mysql_grant { 'test@long.hostname.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@long.hostname.com', + privileges => 'ALL', + require => Mysql_user['test@long.hostname.com'], + } + mysql_user { 'test@192.168.5.6': + ensure => present, + } + mysql_grant { 'test@192.168.5.6/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@192.168.5.6', + privileges => 'ALL', + require => Mysql_user['test@192.168.5.6'], + } + mysql_user { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004': + ensure => present, + } + mysql_grant { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@2607:f0d0:1002:0051:0000:0000:0000:0004', + privileges => 'ALL', + require => Mysql_user['test@2607:f0d0:1002:0051:0000:0000:0000:0004'], + } + mysql_user { 'test@::1/128': + ensure => present, + } + mysql_grant { 'test@::1/128/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@::1/128', + privileges => 'ALL', + require => Mysql_user['test@::1/128'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'finds short hostname' do + shell("mysql -NBe \"SHOW GRANTS FOR test@short\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'short'/) + expect(r.stderr).to be_empty + end + end + it 'finds long hostname' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'long.hostname.com'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'long.hostname.com'/) + expect(r.stderr).to be_empty + end + end + it 'finds ipv4' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.6'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'192.168.5.6'/) + expect(r.stderr).to be_empty + end + end + it 'finds ipv6' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'/) + expect(r.stderr).to be_empty + end + end + it 'finds short ipv6' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'::1/128'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'::1\/128'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'complex test' do + it 'setup mysql::server' do + pp = <<-EOS + $dbSubnet = '10.10.10.%' + + mysql_database { 'foo': + ensure => present, + } + + exec { 'mysql-create-table': + command => '/usr/bin/mysql -NBe "CREATE TABLE foo.bar (name VARCHAR(20))"', + environment => "HOME=${::root_home}", + unless => '/usr/bin/mysql -NBe "SELECT 1 FROM foo.bar LIMIT 1;"', + require => Mysql_database['foo'], + } + + Mysql_grant { + ensure => present, + options => ['GRANT'], + privileges => ['ALL'], + table => '*.*', + require => [ Mysql_database['foo'], Exec['mysql-create-table'] ], + } + + mysql_user { "user1@${dbSubnet}": + ensure => present, + } + mysql_grant { "user1@${dbSubnet}/*.*": + user => "user1@${dbSubnet}", + require => Mysql_user["user1@${dbSubnet}"], + } + mysql_user { "user2@${dbSubnet}": + ensure => present, + } + mysql_grant { "user2@${dbSubnet}/foo.bar": + privileges => ['SELECT', 'INSERT', 'UPDATE'], + user => "user2@${dbSubnet}", + table => 'foo.bar', + require => Mysql_user["user2@${dbSubnet}"], + } + mysql_user { "user3@${dbSubnet}": + ensure => present, + } + mysql_grant { "user3@${dbSubnet}/foo.*": + privileges => ['SELECT', 'INSERT', 'UPDATE'], + user => "user3@${dbSubnet}", + table => 'foo.*', + require => Mysql_user["user3@${dbSubnet}"], + } + mysql_user { 'web@%': + ensure => present, + } + mysql_grant { 'web@%/*.*': + user => 'web@%', + require => Mysql_user['web@%'], + } + mysql_user { "web@${dbSubnet}": + ensure => present, + } + mysql_grant { "web@${dbSubnet}/*.*": + user => "web@${dbSubnet}", + require => Mysql_user["web@${dbSubnet}"], + } + mysql_user { "web@${fqdn}": + ensure => present, + } + mysql_grant { "web@${fqdn}/*.*": + user => "web@${fqdn}", + require => Mysql_user["web@${fqdn}"], + } + mysql_user { 'web@localhost': + ensure => present, + } + mysql_grant { 'web@localhost/*.*': + user => 'web@localhost', + require => Mysql_user['web@localhost'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + end + + describe 'lower case privileges' do + it 'create ALL privs' do + pp = <<-EOS + mysql_user { 'lowercase@localhost': + ensure => present, + } + mysql_grant { 'lowercase@localhost/*.*': + user => 'lowercase@localhost', + privileges => 'ALL', + table => '*.*', + require => Mysql_user['lowercase@localhost'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'create lowercase all privs' do + pp = <<-EOS + mysql_user { 'lowercase@localhost': + ensure => present, + } + mysql_grant { 'lowercase@localhost/*.*': + user => 'lowercase@localhost', + privileges => 'all', + table => '*.*', + require => Mysql_user['lowercase@localhost'], + } + EOS + + expect(apply_manifest(pp, :catch_failures => true).exit_code).to eq(0) + end + end + + describe 'adding procedure privileges' do + it 'should work without errors' do + pp = <<-EOS + if $::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemmajrelease, '16.00') > 0 { + exec { 'simpleproc-create': + command => 'mysql --user="root" --password="password" --database=mysql --delimiter="//" -NBe "CREATE PROCEDURE simpleproc (OUT param1 INT) BEGIN SELECT COUNT(*) INTO param1 FROM t; end//"', + path => '/usr/bin/', + before => Mysql_user['test2@tester'], + } + } + mysql_user { 'test2@tester': + ensure => present, + } + mysql_grant { 'test2@tester/PROCEDURE mysql.simpleproc': + ensure => 'present', + table => 'PROCEDURE mysql.simpleproc', + user => 'test2@tester', + privileges => ['EXECUTE'], + require => Mysql_user['test2@tester'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"SHOW GRANTS FOR test2@tester\"") do |r| + expect(r.stdout).to match(/GRANT EXECUTE ON PROCEDURE `mysql`.`simpleproc` TO 'test2'@'tester'/) + expect(r.stderr).to be_empty + end + end + end + + describe 'grants with skip-name-resolve specified' do + it 'setup mysql::server' do + pp = <<-EOS + class { 'mysql::server': + override_options => { + 'mysqld' => {'skip-name-resolve' => true} + }, + restart => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should apply' do + pp = <<-EOS + mysql_user { 'test@fqdn.com': + ensure => present, + } + mysql_grant { 'test@fqdn.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@fqdn.com', + privileges => 'ALL', + require => Mysql_user['test@fqdn.com'], + } + mysql_user { 'test@192.168.5.7': + ensure => present, + } + mysql_grant { 'test@192.168.5.7/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@192.168.5.7', + privileges => 'ALL', + require => Mysql_user['test@192.168.5.7'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should fail with fqdn' do + pre_run + if ! version_is_greater_than('5.7.0') + expect(shell("mysql -NBe \"SHOW GRANTS FOR test@fqdn.com\"", { :acceptable_exit_codes => 1}).stderr).to match(/There is no such grant defined for user 'test' on host 'fqdn.com'/) + end + end + it 'finds ipv4' do + shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.7'\"") do |r| + expect(r.stdout).to match(/GRANT ALL PRIVILEGES ON `test`.* TO 'test'@'192.168.5.7'/) + expect(r.stderr).to be_empty + end + end + + it 'should fail to execute while applying' do + pp = <<-EOS + mysql_user { 'test@fqdn.com': + ensure => present, + } + mysql_grant { 'test@fqdn.com/test.*': + ensure => 'present', + table => 'test.*', + user => 'test@fqdn.com', + privileges => 'ALL', + require => Mysql_user['test@fqdn.com'], + } + EOS + + mysql_cmd = shell('which mysql').stdout.chomp + shell("mv #{mysql_cmd} #{mysql_cmd}.bak") + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Command mysql is missing/) + shell("mv #{mysql_cmd}.bak #{mysql_cmd}") + end + + it 'reset mysql::server config' do + pp = <<-EOS + class { 'mysql::server': + restart => true, + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'adding privileges to specific table' do + # Using puppet_apply as a helper + it 'setup mysql server' do + pp = <<-EOS + class { 'mysql::server': override_options => { 'root_password' => 'password' } } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'creates grant on missing table will fail' do + pp = <<-EOS + mysql_user { 'test@localhost': + ensure => present, + } + mysql_grant { 'test@localhost/grant_spec_db.grant_spec_table': + user => 'test@localhost', + privileges => ['SELECT'], + table => 'grant_spec_db.grant_spec_table', + require => Mysql_user['test@localhost'], + } + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Table 'grant_spec_db\.grant_spec_table' doesn't exist/) + end + + it 'creates table' do + pp = <<-EOS + file { '/tmp/grant_spec_table.sql': + ensure => file, + content => 'CREATE TABLE grant_spec_table (id int);', + before => Mysql::Db['grant_spec_db'], + } + mysql::db { 'grant_spec_db': + user => 'root1', + password => 'password', + sql => '/tmp/grant_spec_table.sql', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should have the table' do + expect(shell("mysql -e 'show tables;' grant_spec_db|grep grant_spec_table").exit_code).to be_zero + end + end +end diff --git a/environments/production/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb b/environments/production/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb new file mode 100644 index 0000000..538b67c --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/types/mysql_plugin_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper_acceptance' + +# Different operating systems (and therefore different versions/forks +# of mysql) have varying levels of support for plugins and have +# different plugins available. Choose a plugin that works or don't try +# to test plugins if not available. +if fact('osfamily') =~ /RedHat/ + if fact('operatingsystemrelease') =~ /^5\./ + plugin = nil # Plugins not supported on mysql on RHEL 5 + elsif fact('operatingsystemrelease') =~ /^6\./ + plugin = 'example' + plugin_lib = 'ha_example.so' + elsif fact('operatingsystemrelease') =~ /^7\./ + plugin = 'pam' + plugin_lib = 'auth_pam.so' + end +elsif fact('osfamily') =~ /Debian/ + if fact('operatingsystem') =~ /Debian/ + if fact('operatingsystemrelease') =~ /^6\./ + # Only available plugin is innodb which is already loaded and not unload- or reload-able + plugin = nil + elsif fact('operatingsystemrelease') =~ /^7\./ + plugin = 'example' + plugin_lib = 'ha_example.so' + end + elsif fact('operatingsystem') =~ /Ubuntu/ + if fact('operatingsystemrelease') =~ /^10\.04/ + # Only available plugin is innodb which is already loaded and not unload- or reload-able + plugin = nil + elsif fact('operatingsystemrelease') =~ /^16\.04/ + # On Xenial running 5.7.12, the example plugin does not appear to be available. + plugin = 'validate_password' + plugin_lib = 'validate_password.so' + else + plugin = 'example' + plugin_lib = 'ha_example.so' + end + end +elsif fact('osfamily') =~ /Suse/ + plugin = nil # Plugin library path is broken on Suse http://lists.opensuse.org/opensuse-bugs/2013-08/msg01123.html +end + +describe 'mysql_plugin' do + if plugin # if plugins are supported + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + describe 'load plugin' do + it 'should work without errors' do + pp = <<-EOS + mysql_plugin { #{plugin}: + ensure => present, + soname => '#{plugin_lib}', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the plugin' do + shell("mysql -NBe \"select plugin_name from information_schema.plugins where plugin_name='#{plugin}'\"") do |r| + expect(r.stdout).to match(/^#{plugin}$/i) + expect(r.stderr).to be_empty + end + end + end + end + +end diff --git a/environments/production/modules/mysql/spec/acceptance/types/mysql_user_spec.rb b/environments/production/modules/mysql/spec/acceptance/types/mysql_user_spec.rb new file mode 100644 index 0000000..c91f65a --- /dev/null +++ b/environments/production/modules/mysql/spec/acceptance/types/mysql_user_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper_acceptance' + +describe 'mysql_user' do + describe 'setup' do + it 'should work with no errors' do + pp = <<-EOS + class { 'mysql::server': } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + + context 'using ashp@localhost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'has no SSL options' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^\s*$/) + expect(r.stderr).to be_empty + end + end + end + end + + context 'using ashp-dash@localhost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp-dash@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp-dash@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + end + end + + context 'using ashp@LocalHost' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'ashp@LocalHost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + end + end + context 'using resource should throw no errors' do + describe 'find users' do + it { + on default, puppet('resource mysql_user'), {:catch_failures => true} do |r| + expect(r.stdout).to_not match(/Error:/) + expect(r.stdout).to_not match(/must be properly quoted, invalid character:/) + end + } + end + end + context 'using user-w-ssl@localhost with SSL' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'user-w-ssl@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + tls_options => ['SSL'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'should show correct ssl_type' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-ssl@localhost'\"") do |r| + expect(r.stdout).to match(/^ANY$/) + expect(r.stderr).to be_empty + end + end + end + end + context 'using user-w-x509@localhost with X509' do + describe 'adding user' do + it 'should work without errors' do + pp = <<-EOS + mysql_user { 'user-w-x509@localhost': + password_hash => '*F9A8E96790775D196D12F53BCC88B8048FF62ED5', + tls_options => ['X509'], + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should find the user' do + shell("mysql -NBe \"select '1' from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| + expect(r.stdout).to match(/^1$/) + expect(r.stderr).to be_empty + end + end + it 'should show correct ssl_type' do + shell("mysql -NBe \"select SSL_TYPE from mysql.user where CONCAT(user, '@', host) = 'user-w-x509@localhost'\"") do |r| + expect(r.stdout).to match(/^X509$/) + expect(r.stderr).to be_empty + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/graceful_failures_spec.rb b/environments/production/modules/mysql/spec/classes/graceful_failures_spec.rb new file mode 100644 index 0000000..9fb8117 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/graceful_failures_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'mysql::server' do + context "on an unsupported OS" do + # fetch any sets of facts to modify them + os, facts = on_supported_os.first + + let(:facts) { + facts.merge({ + :osfamily => 'UNSUPPORTED', + :operatingsystem => 'UNSUPPORTED', + }) + } + + it 'should gracefully fail' do + is_expected.to compile.and_raise_error(/Unsupported platform:/) + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mycnf_template_spec.rb b/environments/production/modules/mysql/spec/classes/mycnf_template_spec.rb new file mode 100644 index 0000000..9c0c433 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mycnf_template_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'mysql::server' do + context 'my.cnf template' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'normal entry' do + let(:params) {{ :override_options => { 'mysqld' => { 'socket' => '/var/lib/mysql/mysql.sock' } } }} + it do + is_expected.to contain_file('mysql-config-file').with({ + :mode => '0644', + :selinux_ignore_defaults => true, + }).with_content(/socket = \/var\/lib\/mysql\/mysql.sock/) + end + end + + describe 'array entry' do + let(:params) {{ :override_options => { 'mysqld' => { 'replicate-do-db' => ['base1', 'base2'], } }}} + it do + is_expected.to contain_file('mysql-config-file').with_content( + /.*replicate-do-db = base1\nreplicate-do-db = base2.*/ + ) + end + end + + describe 'skip-name-resolve set to an empty string' do + let(:params) {{ :override_options => { 'mysqld' => { 'skip-name-resolve' => '' }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/^skip-name-resolve$/) } + end + + describe 'ssl set to true' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => true }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/ssl/) } + it { is_expected.to contain_file('mysql-config-file').without_content(/ssl = true/) } + end + + describe 'ssl set to false' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => false }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/ssl = false/) } + end + + # ssl-disable (and ssl) are special cased within mysql. + describe 'possibility of disabling ssl completely' do + let(:params) {{ :override_options => { 'mysqld' => { 'ssl' => true, 'ssl-disable' => true }}}} + it { is_expected.to contain_file('mysql-config-file').without_content(/ssl = true/) } + end + + describe 'a non ssl option set to true' do + let(:params) {{ :override_options => { 'mysqld' => { 'test' => true }}}} + it { is_expected.to contain_file('mysql-config-file').with_content(/^test$/) } + it { is_expected.to contain_file('mysql-config-file').without_content(/test = true/) } + end + + context 'with includedir' do + let(:params) {{ :includedir => '/etc/my.cnf.d' }} + it 'makes the directory' do + is_expected.to contain_file('/etc/my.cnf.d').with({ + :ensure => :directory, + :mode => '0755', + }) + end + + it { is_expected.to contain_file('mysql-config-file').with_content(/!includedir/) } + end + + context 'without includedir' do + let(:params) {{ :includedir => '' }} + it 'shouldnt contain the directory' do + is_expected.not_to contain_file('mysql-config-file').with({ + :ensure => :directory, + :mode => '0755', + }) + end + + it { is_expected.to contain_file('mysql-config-file').without_content(/!includedir/) } + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_bindings_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_bindings_spec.rb new file mode 100644 index 0000000..066e876 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_bindings_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'mysql::bindings' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:params) {{ + 'java_enable' => true, + 'perl_enable' => true, + 'php_enable' => true, + 'python_enable' => true, + 'ruby_enable' => true, + 'client_dev' => true, + 'daemon_dev' => true, + 'client_dev_package_name' => 'libmysqlclient-devel', + 'daemon_dev_package_name' => 'mysql-devel', + }} + + it { is_expected.to contain_package('mysql-connector-java') } + it { is_expected.to contain_package('perl_mysql') } + it { is_expected.to contain_package('python-mysqldb') } + it { is_expected.to contain_package('ruby_mysql') } + it { is_expected.to contain_package('mysql-client_dev') } + it { is_expected.to contain_package('mysql-daemon_dev') } + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_client_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_client_spec.rb new file mode 100644 index 0000000..fdbcc19 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_client_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'mysql::client' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'with defaults' do + it { is_expected.not_to contain_class('mysql::bindings') } + it { is_expected.to contain_package('mysql_client') } + end + + context 'with bindings enabled' do + let(:params) {{ :bindings_enable => true }} + + it { is_expected.to contain_class('mysql::bindings') } + it { is_expected.to contain_package('mysql_client') } + end + + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + + it { is_expected.to contain_package('mysql_client') } + end + + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + + it { is_expected.not_to contain_package('mysql_client') } + end + + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_server_account_security_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_server_account_security_spec.rb new file mode 100644 index 0000000..d45c46e --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_server_account_security_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'mysql::server::account_security' do + on_supported_os.each do |os, facts| + context "on #{os}" do + context "with fqdn==myhost.mydomain" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'myhost.mydomain', + :hostname => 'myhost', + }) + } + + [ 'root@myhost.mydomain', + 'root@127.0.0.1', + 'root@::1', + '@myhost.mydomain', + '@localhost', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + + # When the hostname doesn't match the fqdn we also remove these. + # We don't need to test the inverse as when they match they are + # covered by the above list. + [ 'root@myhost', '@myhost' ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + + it 'should remove Mysql_database[test]' do + is_expected.to contain_mysql_database('test').with_ensure('absent') + end + end + + context "with fqdn==localhost" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'localhost', + :hostname => 'localhost', + }) + } + + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + 'root@localhost.localdomain', + '@localhost.localdomain', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + end + + context "with fqdn==localhost.localdomain" do + let(:facts) { + facts.merge({ + :root_home => '/root', + :fqdn => 'localhost.localdomain', + :hostname => 'localhost', + }) + } + + [ 'root@127.0.0.1', + 'root@::1', + '@localhost', + 'root@localhost.localdomain', + '@localhost.localdomain', + '@%', + ].each do |user| + it "removes Mysql_User[#{user}]" do + is_expected.to contain_mysql_user(user).with_ensure('absent') + end + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_server_backup_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_server_backup_spec.rb new file mode 100644 index 0000000..96cfdf4 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_server_backup_spec.rb @@ -0,0 +1,407 @@ +require 'spec_helper' + +describe 'mysql::server::backup' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:default_params) { + { 'backupuser' => 'testuser', + 'backuppassword' => 'testpass', + 'maxallowedpacket' => '1M', + 'backupdir' => '/tmp', + 'backuprotate' => '25', + 'delete_before_dump' => true, + 'execpath' => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', + 'maxallowedpacket' => '1M', + } + } + + context 'standard conditions' do + let(:params) { default_params } + + # Cannot use that_requires here, doesn't work on classes. + it { is_expected.to contain_mysql_user('testuser@localhost').with( + :require => 'Class[Mysql::Server::Root_password]') } + + it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( + :privileges => ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS'] + ).that_requires('Mysql_user[testuser@localhost]') } + + context 'with triggers included' do + let(:params) do + { :include_triggers => true }.merge(default_params) + end + it { is_expected.to contain_mysql_grant('testuser@localhost/*.*').with( + :privileges => ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER'] + ).that_requires('Mysql_user[testuser@localhost]') } + end + + it { is_expected.to contain_cron('mysql-backup').with( + :command => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + )} + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it { is_expected.to contain_file('mysqlbackupdir').with( + :path => '/tmp', + :ensure => 'directory' + )} + + it 'should have compression by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /bzcat -zc/ + ) + end + + it 'should skip backing up events table by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="--ignore-table=mysql.event"/ + ) + end + + it 'should not mention triggers by default because file_per_database is false' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*triggers.*/ + ) + end + + it 'should not mention routines by default because file_per_database is false' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*routines.*/ + ) + end + + it 'should have 25 days of rotation' do + # MySQL counts from 0 + is_expected.to contain_file('mysqlbackup.sh').with_content(/.*ROTATE=24.*/) + end + + it 'should have a standard PATH' do + is_expected.to contain_file('mysqlbackup.sh').with_content(%r{PATH=/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin}) + end + end + + context 'custom ownership and mode for backupdir' do + let(:params) do + { :backupdirmode => '0750', + :backupdirowner => 'testuser', + :backupdirgroup => 'testgrp', + }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackupdir').with( + :path => '/tmp', + :ensure => 'directory', + :mode => '0750', + :owner => 'testuser', + :group => 'testgrp' + ) } + end + + context 'with compression disabled' do + let(:params) do + { :backupcompress => false }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it 'should be able to disable compression' do + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*bzcat -zc.*/ + ) + end + end + + context 'with mysql.events backedup' do + let(:params) do + { :ignore_events => false }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) } + + it 'should be able to backup events table' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="--events"/ + ) + end + end + + context 'with database list specified' do + let(:params) do + { :backupdatabases => ['mysql'] }.merge(default_params) + end + + it { is_expected.to contain_file('mysqlbackup.sh').with( + :path => '/usr/local/sbin/mysqlbackup.sh', + :ensure => 'present' + ) + } + + it 'should have a backup file for each database' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /mysql | bzcat -zc \${DIR}\\\${PREFIX}mysql_`date'/ + ) + end + + it 'should skip backup triggers by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + + it 'should skip backing up routines by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + + context 'with include_triggers set to true' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => true + }) + end + + it 'should backup triggers when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"/ + ) + end + end + + context 'with include_triggers set to false' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => false + }) + end + + it 'should skip backing up triggers when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + end + + context 'with include_routines set to true' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_routines => true + }) + end + + it 'should backup routines when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"/ + ) + end + end + + context 'with include_routines set to false' do + let(:params) do + default_params.merge({ + :backupdatabases => ['mysql'], + :include_triggers => true + }) + end + + it 'should skip backing up routines when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + end + end + + context 'with file per database' do + let(:params) do + default_params.merge({ :file_per_database => true }) + end + + it 'should loop through backup all databases' do + is_expected.to contain_file('mysqlbackup.sh').with_content(/.*SHOW DATABASES.*/) + end + + context 'with compression disabled' do + let(:params) do + default_params.merge({ :file_per_database => true, :backupcompress => false }) + end + + it 'should loop through backup all databases without compression' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /.*SHOW DATABASES.*/ + ) + is_expected.to contain_file('mysqlbackup.sh').without_content( + /.*bzcat -zc.*/ + ) + end + end + + it 'should skip backup triggers by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + + it 'should skip backing up routines by default' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + + context 'with include_triggers set to true' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => true + }) + end + + it 'should backup triggers when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --triggers"/ + ) + end + end + + context 'with include_triggers set to false' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => false + }) + end + + it 'should skip backing up triggers when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-triggers"/ + ) + end + end + + context 'with include_routines set to true' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_routines => true + }) + end + + it 'should backup routines when asked' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --routines"/ + ) + end + end + + context 'with include_routines set to false' do + let(:params) do + default_params.merge({ + :file_per_database => true, + :include_triggers => true + }) + end + + it 'should skip backing up routines when asked to skip' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /ADDITIONAL_OPTIONS="\$ADDITIONAL_OPTIONS --skip-routines"/ + ) + end + end + end + + context 'with postscript' do + let(:params) do + default_params.merge({ :postscript => 'rsync -a /tmp backup01.local-lan:' }) + end + + it 'should be add postscript' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /rsync -a \/tmp backup01.local-lan:/ + ) + end + end + + context 'with postscripts' do + let(:params) do + default_params.merge({ :postscript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ]}) + end + + it 'should be add postscript' do + is_expected.to contain_file('mysqlbackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + + context 'with the xtrabackup provider' do + let(:params) do + default_params.merge({:provider => 'xtrabackup'}) + end + + it 'should contain the wrapper script' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /^innobackupex\s+"\$@"/ + ) + end + + context 'with prescript defined' do + let(:params) do + default_params.merge({ + :provider => 'xtrabackup', + :prescript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ] + }) + end + + it 'should contain the prescript' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + + context 'with postscript defined' do + let(:params) do + default_params.merge({ + :provider => 'xtrabackup', + :postscript => [ + 'rsync -a /tmp backup01.local-lan:', + 'rsync -a /tmp backup02.local-lan:', + ] + }) + end + + it 'should contain the prostscript' do + is_expected.to contain_file('xtrabackup.sh').with_content( + /.*rsync -a \/tmp backup01.local-lan:\n\nrsync -a \/tmp backup02.local-lan:.*/ + ) + end + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_server_monitor_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_server_monitor_spec.rb new file mode 100644 index 0000000..27fab17 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_server_monitor_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +describe 'mysql::server::monitor' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let :pre_condition do + "include 'mysql::server'" + end + + let :default_params do + { + :mysql_monitor_username => 'monitoruser', + :mysql_monitor_password => 'monitorpass', + :mysql_monitor_hostname => 'monitorhost', + } + end + + let :params do + default_params + end + + it { is_expected.to contain_mysql_user('monitoruser@monitorhost')} + + it { is_expected.to contain_mysql_grant('monitoruser@monitorhost/*.*').with( + :ensure => 'present', + :user => 'monitoruser@monitorhost', + :table => '*.*', + :privileges => ["PROCESS", "SUPER"], + :require => 'Mysql_user[monitoruser@monitorhost]' + )} + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb new file mode 100644 index 0000000..4fef3d5 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_server_mysqltuner_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'mysql::server::mysqltuner' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :staging_http_get => 'curl', + :root_home => '/root', + }) + } + + context 'ensure => present' do + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-v1.3.0').with({ + :source => 'https://github.com/major/MySQLTuner-perl/raw/v1.3.0/mysqltuner.pl', + }) + } + end + + context 'ensure => absent' do + let(:params) {{ :ensure => 'absent' }} + it { is_expected.to compile } + it { is_expected.to contain_file('/usr/local/bin/mysqltuner').with(:ensure => 'absent') } + end + + context 'custom version' do + let(:params) {{ :version => 'v1.2.0' }} + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-v1.2.0').with({ + :source => 'https://github.com/major/MySQLTuner-perl/raw/v1.2.0/mysqltuner.pl', + }) + } + end + + context 'custom source' do + let(:params) {{ :source => '/tmp/foo' }} + it { is_expected.to compile } + it { is_expected.to contain_staging__file('mysqltuner-/tmp/foo').with({ + :source => '/tmp/foo', + }) + } + end + end + end +end diff --git a/environments/production/modules/mysql/spec/classes/mysql_server_spec.rb b/environments/production/modules/mysql/spec/classes/mysql_server_spec.rb new file mode 100644 index 0000000..2a1ca62 --- /dev/null +++ b/environments/production/modules/mysql/spec/classes/mysql_server_spec.rb @@ -0,0 +1,220 @@ +require 'spec_helper' + +describe 'mysql::server' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + context 'with defaults' do + it { is_expected.to contain_class('mysql::server::install') } + it { is_expected.to contain_class('mysql::server::config') } + it { is_expected.to contain_class('mysql::server::service') } + it { is_expected.to contain_class('mysql::server::root_password') } + it { is_expected.to contain_class('mysql::server::providers') } + end + + context 'with remove_default_accounts set' do + let(:params) {{ :remove_default_accounts => true }} + it { is_expected.to contain_class('mysql::server::account_security') } + end + + context 'when not managing config file' do + let(:params) {{ :manage_config_file => false }} + it { is_expected.to compile.with_all_deps } + end + + context 'when not managing the service' do + let(:params) {{ :service_manage => false }} + it { is_expected.to compile.with_all_deps } + it { is_expected.not_to contain_service('mysqld') } + end + + context 'mysql::server::install' do + it 'contains the package by default' do + is_expected.to contain_package('mysql-server').with({ + :ensure => :present, + }) + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_package('mysql-server') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.not_to contain_package('mysql-server') } + end + context 'with datadir overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'datadir' => '/tmp' }} }} + it { is_expected.to contain_mysql_datadir('/tmp') } + end + end + + context 'mysql::server::service' do + context 'with defaults' do + it { is_expected.to contain_service('mysqld') } + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_service('mysqld').that_requires('Package[mysql-server]') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.to contain_service('mysqld') } + it { is_expected.not_to contain_service('mysqld').that_requires('Package[mysql-server]') } + end + context 'service_enabled set to false' do + let(:params) {{ :service_enabled => false }} + + it do + is_expected.to contain_service('mysqld').with({ + :ensure => :stopped + }) + end + context 'with package_manage set to true' do + let(:params) {{ :package_manage => true }} + it { is_expected.to contain_package('mysql-server') } + end + context 'with package_manage set to false' do + let(:params) {{ :package_manage => false }} + it { is_expected.not_to contain_package('mysql-server') } + end + context 'with datadir overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'datadir' => '/tmp' }} }} + it { is_expected.to contain_mysql_datadir('/tmp') } + end + end + context 'with log-error overridden' do + let(:params) {{ :override_options => { 'mysqld' => { 'log-error' => '/tmp/error.log' }} }} + it { is_expected.to contain_file('/tmp/error.log') } + end + end + + context 'mysql::server::root_password' do + describe 'when defaults' do + it { + is_expected.to contain_exec('remove install pass').with( + :command => 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /.mysql_secret) password \'\' && rm -f /.mysql_secret', + :onlyif => 'test -f /.mysql_secret', + :path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + ) + } + it { is_expected.not_to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when root_password set' do + let(:params) {{:root_password => 'SET' }} + it { is_expected.to contain_mysql_user('root@localhost') } + if Puppet.version.to_f >= 3.0 + it { is_expected.to contain_file('/root/.my.cnf').with(:show_diff => false).that_requires('Mysql_user[root@localhost]') } + else + it { is_expected.to contain_file('/root/.my.cnf').that_requires('Mysql_user[root@localhost]') } + end + end + describe 'when root_password set, create_root_user set to false' do + let(:params) {{ :root_password => 'SET', :create_root_user => false }} + it { is_expected.not_to contain_mysql_user('root@localhost') } + if Puppet.version.to_f >= 3.0 + it { is_expected.to contain_file('/root/.my.cnf').with(:show_diff => false) } + else + it { is_expected.to contain_file('/root/.my.cnf') } + end + end + describe 'when root_password set, create_root_my_cnf set to false' do + let(:params) {{ :root_password => 'SET', :create_root_my_cnf => false }} + it { is_expected.to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when root_password set, create_root_user and create_root_my_cnf set to false' do + let(:params) {{ :root_password => 'SET', :create_root_user => false, :create_root_my_cnf => false }} + it { is_expected.not_to contain_mysql_user('root@localhost') } + it { is_expected.not_to contain_file('/root/.my.cnf') } + end + describe 'when install_secret_file set to /root/.mysql_secret' do + let(:params) {{ :install_secret_file => '/root/.mysql_secret' }} + it { + is_expected.to contain_exec('remove install pass').with( + :command => 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /root/.mysql_secret) password \'\' && rm -f /root/.mysql_secret', + :onlyif => 'test -f /root/.mysql_secret' + ) + } + end + end + + context 'mysql::server::providers' do + describe 'with users' do + let(:params) {{:users => { + 'foo@localhost' => { + 'max_connections_per_hour' => '1', + 'max_queries_per_hour' => '2', + 'max_updates_per_hour' => '3', + 'max_user_connections' => '4', + 'password_hash' => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' + }, + 'foo2@localhost' => {} + }}} + it { is_expected.to contain_mysql_user('foo@localhost').with( + :max_connections_per_hour => '1', + :max_queries_per_hour => '2', + :max_updates_per_hour => '3', + :max_user_connections => '4', + :password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' + )} + it { is_expected.to contain_mysql_user('foo2@localhost').with( + :max_connections_per_hour => nil, + :max_queries_per_hour => nil, + :max_updates_per_hour => nil, + :max_user_connections => nil, + :password_hash => nil + )} + end + + describe 'with grants' do + let(:params) {{:grants => { + 'foo@localhost/somedb.*' => { + 'user' => 'foo@localhost', + 'table' => 'somedb.*', + 'privileges' => ["SELECT", "UPDATE"], + 'options' => ["GRANT"], + }, + 'foo2@localhost/*.*' => { + 'user' => 'foo2@localhost', + 'table' => '*.*', + 'privileges' => ["SELECT"], + }, + }}} + it { is_expected.to contain_mysql_grant('foo@localhost/somedb.*').with( + :user => 'foo@localhost', + :table => 'somedb.*', + :privileges => ["SELECT", "UPDATE"], + :options => ["GRANT"] + )} + it { is_expected.to contain_mysql_grant('foo2@localhost/*.*').with( + :user => 'foo2@localhost', + :table => '*.*', + :privileges => ["SELECT"], + :options => nil + )} + end + + describe 'with databases' do + let(:params) {{:databases => { + 'somedb' => { + 'charset' => 'latin1', + 'collate' => 'latin1', + }, + 'somedb2' => {} + }}} + it { is_expected.to contain_mysql_database('somedb').with( + :charset => 'latin1', + :collate => 'latin1' + )} + it { is_expected.to contain_mysql_database('somedb2')} + end + end + end + end +end diff --git a/environments/production/modules/mysql/spec/defines/mysql_db_spec.rb b/environments/production/modules/mysql/spec/defines/mysql_db_spec.rb new file mode 100644 index 0000000..0cd9cb2 --- /dev/null +++ b/environments/production/modules/mysql/spec/defines/mysql_db_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe 'mysql::db', :type => :define do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { + facts.merge({ + :root_home => '/root', + }) + } + + let(:title) { 'test_db' } + + let(:params) { + { 'user' => 'testuser', + 'password' => 'testpass', + } + } + + it 'should report an error when ensure is not present or absent' do + params.merge!({'ensure' => 'invalid_val'}) + expect { catalogue }.to raise_error(Puppet::Error, + /invalid_val is not supported for ensure\. Allowed values are 'present' and 'absent'\./) + end + + it 'should not notify the import sql exec if no sql script was provided' do + is_expected.to contain_mysql_database('test_db').without_notify + end + + it 'should subscribe to database if sql script is given' do + params.merge!({'sql' => 'test_sql'}) + is_expected.to contain_exec('test_db-import').with_subscribe('Mysql_database[test_db]') + end + + it 'should only import sql script on creation if not enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => false}) + is_expected.to contain_exec('test_db-import').with_refreshonly(true) + end + + it 'should import sql script on creation if enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => true}) + is_expected.to contain_exec('test_db-import').with_refreshonly(false) + is_expected.to contain_exec('test_db-import').with_command('cat test_sql | mysql test_db') + end + + it 'should import sql script with custom command on creation if enforcing' do + params.merge!({'sql' => 'test_sql', 'enforce_sql' => true, 'import_cat_cmd' => 'zcat'}) + is_expected.to contain_exec('test_db-import').with_refreshonly(false) + is_expected.to contain_exec('test_db-import').with_command('zcat test_sql | mysql test_db') + end + + it 'should import sql scripts when more than one is specified' do + params.merge!({'sql' => ['test_sql', 'test_2_sql']}) + is_expected.to contain_exec('test_db-import').with_command('cat test_sql test_2_sql | mysql test_db') + end + + it 'should report an error if sql isn\'t a string or an array' do + params.merge!({'sql' => {'foo' => 'test_sql', 'bar' => 'test_2_sql'}}) + expect { catalogue }.to raise_error(Puppet::Error, + /\$sql must be either a string or an array\./) + end + + it 'should not create database and database user' do + params.merge!({'ensure' => 'absent', 'host' => 'localhost'}) + is_expected.to contain_mysql_database('test_db').with_ensure('absent') + is_expected.to contain_mysql_user('testuser@localhost').with_ensure('absent') + end + + it 'should create with an appropriate collate and charset' do + params.merge!({'charset' => 'utf8', 'collate' => 'utf8_danish_ci'}) + is_expected.to contain_mysql_database('test_db').with({ + 'charset' => 'utf8', + 'collate' => 'utf8_danish_ci', + }) + end + + it 'should use dbname parameter as database name instead of name' do + params.merge!({'dbname' => 'real_db'}) + is_expected.to contain_mysql_database('real_db') + end + end + end +end diff --git a/environments/production/modules/mysql/spec/spec.opts b/environments/production/modules/mysql/spec/spec.opts new file mode 100644 index 0000000..91cd642 --- /dev/null +++ b/environments/production/modules/mysql/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/environments/production/modules/mysql/spec/spec_helper.rb b/environments/production/modules/mysql/spec/spec_helper.rb new file mode 100644 index 0000000..22d5d68 --- /dev/null +++ b/environments/production/modules/mysql/spec/spec_helper.rb @@ -0,0 +1,8 @@ +#This file is generated by ModuleSync, do not edit. +require 'puppetlabs_spec_helper/module_spec_helper' + +# put local configuration and setup into spec_helper_local +begin + require 'spec_helper_local' +rescue LoadError +end diff --git a/environments/production/modules/mysql/spec/spec_helper_acceptance.rb b/environments/production/modules/mysql/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..07e19bd --- /dev/null +++ b/environments/production/modules/mysql/spec/spec_helper_acceptance.rb @@ -0,0 +1,66 @@ +require 'beaker-rspec' +require 'beaker/puppet_install_helper' + +run_puppet_install_helper + +UNSUPPORTED_PLATFORMS = [ 'Windows', 'Solaris', 'AIX' ] + +RSpec.configure do |c| + # Project root + proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + + # Readable test descriptions + c.formatter = :documentation + + # detect the situation where PUP-5016 is triggered and skip the idempotency tests in that case + # also note how fact('puppetversion') is not available because of PUP-4359 + if fact('osfamily') == 'Debian' && fact('operatingsystemmajrelease') == '8' && shell('puppet --version').stdout =~ /^4\.2/ + c.filter_run_excluding :skip_pup_5016 => true + end + + # Configure all nodes in nodeset + c.before :suite do + # Install module and dependencies + puppet_module_install(:source => proj_root, :module_name => 'mysql') + hosts.each do |host| + # Required for binding tests. + if fact('osfamily') == 'RedHat' + version = fact("operatingsystemmajrelease") + if fact('operatingsystemmajrelease') =~ /7/ || fact('operatingsystem') =~ /Fedora/ + shell("yum install -y bzip2") + end + end + + # Solaris 11 doesn't ship the SSL CA root for the forgeapi server + # therefore we need to use a different way to deploy the module to + # the host + if host['platform'] =~ /solaris-11/i + apply_manifest_on(host, 'package { "git": }') + # PE 3.x and 2015.2 require different locations to install modules + modulepath = host.puppet['modulepath'] + modulepath = modulepath.split(':').first if modulepath + + environmentpath = host.puppet['environmentpath'] + environmentpath = environmentpath.split(':').first if environmentpath + + destdir = modulepath || "#{environmentpath}/production/modules" + on host, "git clone https://github.com/puppetlabs/puppetlabs-stdlib #{destdir}/stdlib && cd #{destdir}/stdlib && git checkout 3.2.0" + on host, "git clone https://github.com/stahnma/puppet-module-epel.git #{destdir}/epel && cd #{destdir}/epel && git checkout 1.0.2" + else + on host, puppet('module','install','puppetlabs-stdlib','--version','3.2.0') + on host, puppet('module','install','stahnma/epel') + on host, puppet('module','install','puppet/staging') + end + end + end +end + +shared_examples "a idempotent resource" do + it 'should apply with no errors' do + apply_manifest(pp, :catch_failures => true) + end + + it 'should apply a second time without changes', :skip_pup_5016 do + apply_manifest(pp, :catch_changes => true) + end +end diff --git a/environments/production/modules/mysql/spec/spec_helper_local.rb b/environments/production/modules/mysql/spec/spec_helper_local.rb new file mode 100644 index 0000000..9a86ccd --- /dev/null +++ b/environments/production/modules/mysql/spec/spec_helper_local.rb @@ -0,0 +1,3 @@ +require 'rspec-puppet-facts' +include RspecPuppetFacts + diff --git a/environments/production/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb b/environments/production/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb new file mode 100644 index 0000000..a2e9fdf --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/facter/mysql_server_id_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysql_server_id" do + context "igalic's laptop" do + before :each do + Facter.fact(:macaddress).stubs(:value).returns('3c:97:0e:69:fb:e1') + end + it do + Facter.fact(:mysql_server_id).value.to_s.should == '4116385' + end + end + + context "node with lo only" do + before :each do + Facter.fact(:macaddress).stubs(:value).returns('00:00:00:00:00:00') + end + it do + Facter.fact(:mysql_server_id).value.to_s.should == '0' + end + end + end +end diff --git a/environments/production/modules/mysql/spec/unit/facter/mysql_version_spec.rb b/environments/production/modules/mysql/spec/unit/facter/mysql_version_spec.rb new file mode 100644 index 0000000..60379f6 --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/facter/mysql_version_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysql_version" do + context 'with value' do + before :each do + Facter::Util::Resolution.stubs(:exec).with('mysql --version').returns('mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1') + end + it { + expect(Facter.fact(:mysql_version).value).to eq('5.0.95') + } + end + + end + +end diff --git a/environments/production/modules/mysql/spec/unit/facter/mysqld_version_spec.rb b/environments/production/modules/mysql/spec/unit/facter/mysqld_version_spec.rb new file mode 100644 index 0000000..f0b606f --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/facter/mysqld_version_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe Facter::Util::Fact do + before { + Facter.clear + } + + describe "mysqld_version" do + context 'with value' do + before :each do + Facter::Util::Resolution.stubs(:exec).with('mysqld -V 2>/dev/null').returns('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') + end + it { + expect(Facter.fact(:mysqld_version).value).to eq('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') + } + end + + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb new file mode 100644 index 0000000..18cb26b --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_deepmerge_spec.rb @@ -0,0 +1,91 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:mysql_deepmerge) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe 'when calling mysql_deepmerge from puppet' do + it "should not compile when no arguments are passed" do + skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = '$x = mysql_deepmerge()' + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + + it "should not compile when 1 argument is passed" do + skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = "$my_hash={'one' => 1}\n$x = mysql_deepmerge($my_hash)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end + + describe 'when calling mysql_deepmerge on the scope instance' do + it 'should require all parameters are hashes' do + expect { new_hash = scope.function_mysql_deepmerge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/) + expect { new_hash = scope.function_mysql_deepmerge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) + end + + it 'should accept empty strings as puppet undef' do + expect { new_hash = scope.function_mysql_deepmerge([{}, ''])}.not_to raise_error + end + + it 'should be able to mysql_deepmerge two hashes' do + new_hash = scope.function_mysql_deepmerge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}]) + expect(new_hash['one']).to eq('1') + expect(new_hash['two']).to eq('2') + expect(new_hash['three']).to eq('2') + end + + it 'should mysql_deepmerge multiple hashes' do + hash = scope.function_mysql_deepmerge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}]) + expect(hash['one']).to eq('3') + end + + it 'should accept empty hashes' do + expect(scope.function_mysql_deepmerge([{},{},{}])).to eq({}) + end + + it 'should mysql_deepmerge subhashes' do + hash = scope.function_mysql_deepmerge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }]) + expect(hash['one']).to eq(1) + expect(hash['two']).to eq(2) + expect(hash['three']).to eq({ 'four' => 4 }) + end + + it 'should append to subhashes' do + hash = scope.function_mysql_deepmerge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }]) + expect(hash['one']).to eq({ 'two' => 2, 'three' => 3 }) + end + + it 'should append to subhashes 2' do + hash = scope.function_mysql_deepmerge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }]) + expect(hash['one']).to eq(1) + expect(hash['two']).to eq('dos') + expect(hash['three']).to eq({ 'four' => 4, 'five' => 5 }) + end + + it 'should append to subhashes 3' do + hash = scope.function_mysql_deepmerge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }]) + expect(hash['key1']).to eq({ 'a' => 1, 'b' => 99 }) + expect(hash['key2']).to eq({ 'c' => 3 }) + end + + it 'should equate keys mod dash and underscore' do + hash = scope.function_mysql_deepmerge([{ 'a-b-c' => 1 } , { 'a_b_c' => 10 }]) + expect(hash['a_b_c']).to eq(10) + expect(hash).not_to have_key('a-b-c') + end + + it 'should keep style of the last when keys are euqal mod dash and underscore' do + hash = scope.function_mysql_deepmerge([{ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 }} , { 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 } }]) + expect(hash['a_b_c']).to eq(10) + expect(hash).not_to have_key('a-b-c') + expect(hash['b-c-d']).to eq({ 'e-f-g' => 3, 'c_d_e' => 12 }) + expect(hash).not_to have_key('b_c_d') + end + end +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb new file mode 100644 index 0000000..2d5feea --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/functions/mysql_password_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'the mysql_password function' do + before :all do + Puppet::Parser::Functions.autoloader.loadall + end + + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it 'should exist' do + expect(Puppet::Parser::Functions.function('mysql_password')).to eq('function_mysql_password') + end + + it 'should raise a ParseError if there is less than 1 arguments' do + expect { scope.function_mysql_password([]) }.to( raise_error(Puppet::ParseError)) + end + + it 'should raise a ParseError if there is more than 1 arguments' do + expect { scope.function_mysql_password(%w(foo bar)) }.to( raise_error(Puppet::ParseError)) + end + + it 'should convert password into a hash' do + result = scope.function_mysql_password(%w(password)) + expect(result).to(eq('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) + end + + it 'should convert an empty password into a empty string' do + result = scope.function_mysql_password([""]) + expect(result).to(eq('')) + end + + it 'should not convert a password that is already a hash' do + result = scope.function_mysql_password(['*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19']) + expect(result).to(eq('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb new file mode 100644 index 0000000..465e59d --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_database/mysql_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_database).provider(:mysql) do + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + + let(:raw_databases) do + <<-SQL_OUTPUT +information_schema +mydb +mysql +performance_schema +test + SQL_OUTPUT + end + + let(:parsed_databases) { %w(information_schema mydb mysql performance_schema test) } + + let(:resource) { Puppet::Type.type(:mysql_database).new( + { :ensure => :present, + :charset => 'latin1', + :collate => 'latin1_swedish_ci', + :name => 'new_database', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + Facter.stubs(:value).with(:root_home).returns('/root') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show databases']).returns('new_database') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "show variables like '%_database'", 'new_database']).returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") + end + + let(:instance) { provider.class.instances.first } + + describe 'self.instances' do + it 'returns an array of databases' do + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show databases']).returns(raw_databases) + raw_databases.each_line do |db| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "show variables like '%_database'", db.chomp]).returns("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") + end + databases = provider.class.instances.collect {|x| x.name } + expect(parsed_databases).to match_array(databases) + end + end + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'makes a database' do + provider.expects(:mysql).with([defaults_file, '-NBe', "create database if not exists `#{resource[:name]}` character set `#{resource[:charset]}` collate `#{resource[:collate]}`"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'removes a database if present' do + provider.expects(:mysql).with([defaults_file, '-NBe', "drop database if exists `#{resource[:name]}`"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if database exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.stubs(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'charset' do + it 'returns a charset' do + expect(instance.charset).to eq('latin1') + end + end + + describe 'charset=' do + it 'changes the charset' do + provider.expects(:mysql).with([defaults_file, '-NBe', "alter database `#{resource[:name]}` CHARACTER SET blah"]).returns('0') + + provider.charset=('blah') + end + end + + describe 'collate' do + it 'returns a collate' do + expect(instance.collate).to eq('latin1_swedish_ci') + end + end + + describe 'collate=' do + it 'changes the collate' do + provider.expects(:mysql).with([defaults_file, '-NBe', "alter database `#{resource[:name]}` COLLATE blah"]).returns('0') + + provider.collate=('blah') + end + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb new file mode 100644 index 0000000..340562f --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_plugin).provider(:mysql) do + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + + let(:resource) { Puppet::Type.type(:mysql_plugin).new( + { :ensure => :present, + :soname => 'auth_socket.so', + :name => 'auth_socket', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + Facter.stubs(:value).with(:root_home).returns('/root') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', 'show plugins']).returns('auth_socket ACTIVE AUTHENTICATION auth_socket.so GPL') + end + + let(:instance) { provider.class.instances.first } + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'loads a plugin' do + provider.expects(:mysql).with([defaults_file, '-NBe', "install plugin #{resource[:name]} soname '#{resource[:soname]}'"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'unloads a plugin if present' do + provider.expects(:mysql).with([defaults_file, '-NBe', "uninstall plugin #{resource[:name]}"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if plugin exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.stubs(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'soname' do + it 'returns a soname' do + expect(instance.soname).to eq('auth_socket.so') + end + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb new file mode 100644 index 0000000..d993f42 --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/provider/mysql_user/mysql_spec.rb @@ -0,0 +1,345 @@ +require 'spec_helper' + +describe Puppet::Type.type(:mysql_user).provider(:mysql) do + + # Output of mysqld -V + mysql_version_string_hash = { + 'mysql-5.5' => + { + :version => '5.5.46', + :string => '/usr/sbin/mysqld Ver 5.5.46-log for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.6' => + { + :version => '5.6.27', + :string => '/usr/sbin/mysqld Ver 5.6.27 for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.7.1' => + { + :version => '5.7.1', + :string => '/usr/sbin/mysqld Ver 5.7.1 for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mysql-5.7.6' => + { + :version => '5.7.8', + :string => '/usr/sbin/mysqld Ver 5.7.8-rc for Linux on x86_64 (MySQL Community Server (GPL))', + :mysql_type => 'mysql', + }, + 'mariadb-10.0' => + { + :version => '10.0.21', + :string => '/usr/sbin/mysqld Ver 10.0.21-MariaDB for Linux on x86_64 (MariaDB Server)', + :mysql_type => 'mariadb', + }, + 'mariadb-10.0-deb8' => + { + :version => '10.0.23', + :string => '/usr/sbin/mysqld (mysqld 10.0.23-MariaDB-0+deb8u1)', + :mysql_type => 'mariadb', + }, + 'percona-5.5' => + { + :version => '5.5.39', + :string => 'mysqld Ver 5.5.39-36.0-55 for Linux on x86_64 (Percona XtraDB Cluster (GPL), Release rel36.0, Revision 824, WSREP version 25.11, wsrep_25.11.r4023)', + :mysql_type => 'percona', + }, + } + + let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } + let(:system_database) { '--database=mysql' } + let(:newhash) { '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5' } + + let(:raw_users) do + <<-SQL_OUTPUT +root@127.0.0.1 +root@::1 +@localhost +debian-sys-maint@localhost +root@localhost +usvn_user@localhost +@vagrant-ubuntu-raring-64 + SQL_OUTPUT + end + + let(:parsed_users) { %w(root@127.0.0.1 root@::1 @localhost debian-sys-maint@localhost root@localhost usvn_user@localhost @vagrant-ubuntu-raring-64) } + + let(:resource) { Puppet::Type.type(:mysql_user).new( + { :ensure => :present, + :password_hash => '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4', + :name => 'joe@localhost', + :max_user_connections => '10', + :max_connections_per_hour => '10', + :max_queries_per_hour => '10', + :max_updates_per_hour => '10', + :provider => described_class.name + } + )} + let(:provider) { resource.provider } + + before :each do + # Set up the stubs for an instances call. + Facter.stubs(:value).with(:root_home).returns('/root') + Facter.stubs(:value).with(:mysql_version).returns('5.6.24') + provider.class.instance_variable_set(:@mysqld_version_string, '5.6.24') + Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') + Puppet::Util.stubs(:which).with('mysqld').returns('/usr/sbin/mysqld') + File.stubs(:file?).with('/root/.my.cnf').returns(true) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns('joe@localhost') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'"]).returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') + end + + let(:instance) { provider.class.instances.first } + + describe 'self.instances' do + it 'returns an array of users MySQL 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL >= 5.7.0 < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users MySQL >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users mariadb 10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + it 'returns an array of users percona 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) + parsed_users.each do |user| + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + end + + usernames = provider.class.instances.collect {|x| x.name } + expect(parsed_users).to match_array(usernames) + end + end + + describe 'mysql version and type detection' do + mysql_version_string_hash.each do |name,line| + version=line[:version] + string=line[:string] + mysql_type=line[:mysql_type] + it "detects type '#{mysql_type}' with version '#{version}'" do + provider.class.instance_variable_set(:@mysqld_version_string, string) + expect(provider.mysqld_version).to eq(version) + expect(provider.mysqld_type).to eq(mysql_type) + end + end + end + + describe 'self.prefetch' do + it 'exists' do + provider.class.instances + provider.class.prefetch({}) + end + end + + describe 'create' do + it 'makes a user' do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "CREATE USER 'joe'@'localhost' IDENTIFIED BY PASSWORD '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'"]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10"]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]) + provider.expects(:exists?).returns(true) + expect(provider.create).to be_truthy + end + end + + describe 'destroy' do + it 'removes a user if present' do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "DROP USER 'joe'@'localhost'"]) + provider.expects(:exists?).returns(false) + expect(provider.destroy).to be_truthy + end + end + + describe 'exists?' do + it 'checks if user exists' do + expect(instance.exists?).to be_truthy + end + end + + describe 'self.mysqld_version' do + it 'uses the mysqld_version fact if unset' do + provider.class.instance_variable_set(:@mysqld_version_string, nil) + Facter.stubs(:value).with(:mysqld_version).returns('5.6.24') + expect(provider.mysqld_version).to eq '5.6.24' + end + it 'returns 5.7.6 for "mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))"' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6 for Linux on x86_64 (MySQL Community Server (GPL))') + expect(provider.mysqld_version).to eq '5.7.6' + end + it 'returns 5.7.6 for "mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))"' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.6-rc for Linux on x86_64 (MySQL Community Server (GPL))') + expect(provider.mysqld_version).to eq '5.7.6' + end + it 'detects >= 5.7.6 for 5.7.7-log' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.7-log for Linux on x86_64 (MySQL Community Server (GPL))') + expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be >= 0 + end + it 'detects < 5.7.6 for 5.7.5-log' do + provider.class.instance_variable_set(:@mysqld_version_string, 'mysqld Ver 5.7.5-log for Linux on x86_64 (MySQL Community Server (GPL))') + expect(Puppet::Util::Package.versioncmp(provider.mysqld_version, '5.7.6')).to be < 0 + end + end + + describe 'self.defaults_file' do + it 'sets --defaults-extra-file' do + File.stubs(:file?).with('/root/.my.cnf').returns(true) + expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' + end + it 'fails if file missing' do + File.expects(:file?).with('/root/.my.cnf').returns(false) + expect(provider.defaults_file).to be_nil + end + end + + describe 'password_hash' do + it 'returns a hash' do + expect(instance.password_hash).to eq('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') + end + end + + describe 'password_hash=' do + it 'changes the hash mysql 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mysql 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mysql < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash MySQL >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "ALTER USER 'joe'@'localhost' IDENTIFIED WITH mysql_native_password AS '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash mariadb-10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + it 'changes the hash percona-5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'"]).returns('0') + + provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + provider.password_hash=('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5') + end + end + + describe 'tls_options=' do + it 'adds SSL option grant in mysql 5.5' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.5'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql 5.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql < 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.1'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mysql >= 5.7.6' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mysql-5.7.6'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "ALTER USER 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + it 'adds SSL option grant in mariadb-10.0' do + provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.0'][:string]) + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' REQUIRE NONE"]).returns('0') + + provider.expects(:tls_options).returns(['NONE']) + provider.tls_options=(['NONE']) + end + end + + ['max_user_connections', 'max_connections_per_hour', 'max_queries_per_hour', + 'max_updates_per_hour'].each do |property| + + describe property do + it "returns #{property}" do + expect(instance.send("#{property}".to_sym)).to eq('10') + end + end + + describe "#{property}=" do + it "changes #{property}" do + provider.expects(:mysql).with([defaults_file, system_database, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' WITH #{property.upcase} 42"]).returns('0') + provider.expects(property.to_sym).returns('42') + provider.send("#{property}=".to_sym, '42') + end + end + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb new file mode 100644 index 0000000..7897d81 --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_database_spec.rb @@ -0,0 +1,29 @@ +require 'puppet' +require 'puppet/type/mysql_database' +describe Puppet::Type.type(:mysql_database) do + + before :each do + @user = Puppet::Type.type(:mysql_database).new(:name => 'test', :charset => 'utf8', :collate => 'utf8_blah_ci') + end + + it 'should accept a database name' do + expect(@user[:name]).to eq('test') + end + + it 'should accept a charset' do + @user[:charset] = 'latin1' + expect(@user[:charset]).to eq('latin1') + end + + it 'should accept a collate' do + @user[:collate] = 'latin1_swedish_ci' + expect(@user[:collate]).to eq('latin1_swedish_ci') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_database).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb new file mode 100644 index 0000000..9b33058 --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_grant_spec.rb @@ -0,0 +1,74 @@ +require 'puppet' +require 'puppet/type/mysql_grant' +describe Puppet::Type.type(:mysql_grant) do + + before :each do + @user = Puppet::Type.type(:mysql_grant).new(:name => 'foo@localhost/*.*', :privileges => ['ALL', 'PROXY'], :table => ['*.*','@'], :user => 'foo@localhost') + end + + it 'should accept a grant name' do + expect(@user[:name]).to eq('foo@localhost/*.*') + end + + it 'should accept ALL privileges' do + @user[:privileges] = 'ALL' + expect(@user[:privileges]).to eq(['ALL']) + end + + it 'should accept PROXY privilege' do + @user[:privileges] = 'PROXY' + expect(@user[:privileges]).to eq(['PROXY']) + end + + it 'should accept a table' do + @user[:table] = '*.*' + expect(@user[:table]).to eq('*.*') + end + + it 'should accept @ for table' do + @user[:table] = '@' + expect(@user[:table]).to eq('@') + end + + it 'should accept a user' do + @user[:user] = 'foo@localhost' + expect(@user[:user]).to eq('foo@localhost') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_grant).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + it 'should require the name to match the user and table' do + expect { + Puppet::Type.type(:mysql_grant).new(:name => 'foo', :privileges => ['ALL', 'PROXY'], :table => ['*.*','@'], :user => 'foo@localhost') + }.to raise_error /name must match user and table parameters/ + end + + describe 'it should munge privileges' do + + it 'to just ALL' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['ALL', 'PROXY'] ) + expect(@user[:privileges]).to eq(['ALL']) + end + + it 'to upcase and ordered' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['select', 'Insert'] ) + expect(@user[:privileges]).to eq(['INSERT', 'SELECT']) + end + + it 'ordered including column privileges' do + @user = Puppet::Type.type(:mysql_grant).new( + :name => 'foo@localhost/*.*', :table => ['*.*','@'], :user => 'foo@localhost', + :privileges => ['SELECT(Host,Address)', 'Insert'] ) + expect(@user[:privileges]).to eq(['INSERT', 'SELECT (Address, Host)']) + end + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb new file mode 100644 index 0000000..e94c518 --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_plugin_spec.rb @@ -0,0 +1,24 @@ +require 'puppet' +require 'puppet/type/mysql_plugin' +describe Puppet::Type.type(:mysql_plugin) do + + before :each do + @plugin = Puppet::Type.type(:mysql_plugin).new(:name => 'test', :soname => 'test.so') + end + + it 'should accept a plugin name' do + expect(@plugin[:name]).to eq('test') + end + + it 'should accept a library name' do + @plugin[:soname] = 'test.so' + expect(@plugin[:soname]).to eq('test.so') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_plugin).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + +end diff --git a/environments/production/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb new file mode 100644 index 0000000..3e3ae7d --- /dev/null +++ b/environments/production/modules/mysql/spec/unit/puppet/type/mysql_user_spec.rb @@ -0,0 +1,136 @@ +require 'puppet' +require 'puppet/type/mysql_user' +describe Puppet::Type.type(:mysql_user) do + + context "On MySQL 5.x" do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("5.6.24") + end + it 'should fail with a long user name' do + expect { + Puppet::Type.type(:mysql_user).new({:name => '12345678901234567@localhost', :password_hash => 'pass'}) + }.to raise_error /MySQL usernames are limited to a maximum of 16 characters/ + end + end + + context "On MariaDB 10.0.0+" do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("10.0.19") + @user = Puppet::Type.type(:mysql_user).new(:name => '12345678901234567@localhost', :password_hash => 'pass') + end + it 'should succeed with a long user name on MariaDB' do + expect(@user[:name]).to eq('12345678901234567@localhost') + end + end + + it 'should require a name' do + expect { + Puppet::Type.type(:mysql_user).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + context 'using foo@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('foo@localhost') + end + + it 'should accept a password' do + @user[:password_hash] = 'foo' + expect(@user[:password_hash]).to eq('foo') + end + end + + context 'using foo@LocalHost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@LocalHost', :password_hash => 'pass') + end + + it 'should lowercase the user name' do + expect(@user[:name]).to eq('foo@localhost') + end + end + + context 'using foo@192.168.1.0/255.255.255.0' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'foo@192.168.1.0/255.255.255.0', :password_hash => 'pass') + end + + it 'should create the user with the netmask' do + expect(@user[:name]).to eq('foo@192.168.1.0/255.255.255.0') + end + end + + context 'using allo_wed$char@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'allo_wed$char@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('allo_wed$char@localhost') + end + end + + context 'ensure the default \'debian-sys-main\'@localhost user can be parsed' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '\'debian-sys-maint\'@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('\'debian-sys-maint\'@localhost') + end + end + + context 'using a quoted 16 char username' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '"debian-sys-maint"@localhost', :password_hash => 'pass') + end + + it 'should accept a user name' do + expect(@user[:name]).to eq('"debian-sys-maint"@localhost') + end + end + + context 'using a quoted username that is too long ' do + before :each do + Facter.stubs(:value).with(:mysql_version).returns("5.6.24") + end + + it 'should fail with a size error' do + expect { + Puppet::Type.type(:mysql_user).new(:name => '"debian-sys-maint2"@localhost', :password_hash => 'pass') + }.to raise_error /MySQL usernames are limited to a maximum of 16 characters/ + end + end + + context 'using `speci!al#`@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => '`speci!al#`@localhost', :password_hash => 'pass') + end + + it 'should accept a quoted user name with special chatracters' do + expect(@user[:name]).to eq('`speci!al#`@localhost') + end + end + + context 'using in-valid@localhost' do + before :each do + @user = Puppet::Type.type(:mysql_user).new(:name => 'in-valid@localhost', :password_hash => 'pass') + end + + it 'should accept a user name with special chatracters' do + expect(@user[:name]).to eq('in-valid@localhost') + end + end + + context 'using "misquoted@localhost' do + it 'should fail with a misquoted username is used' do + expect { + Puppet::Type.type(:mysql_user).new(:name => '"misquoted@localhost', :password_hash => 'pass') + }.to raise_error /Invalid database user "misquoted@localhost/ + end + end +end diff --git a/environments/production/modules/mysql/templates/meb.cnf.erb b/environments/production/modules/mysql/templates/meb.cnf.erb new file mode 100644 index 0000000..d157af9 --- /dev/null +++ b/environments/production/modules/mysql/templates/meb.cnf.erb @@ -0,0 +1,18 @@ +### MANAGED BY PUPPET ### + +<% @options.sort.map do |k,v| -%> +<% if v.is_a?(Hash) -%> +[<%= k %>] +<% v.sort.map do |ki, vi| -%> +<% if vi == true or v == '' -%> +<%= ki %> +<% elsif vi.is_a?(Array) -%> +<% vi.each do |vii| -%> +<%= ki %> = <%= vii %> +<% end -%> +<% elsif ![nil, '', :undef].include?(vi) -%> +<%= ki %> = <%= vi %> +<% end -%> +<% end -%> +<% end %> +<% end -%> diff --git a/environments/production/modules/mysql/templates/my.cnf.erb b/environments/production/modules/mysql/templates/my.cnf.erb new file mode 100644 index 0000000..7d1f148 --- /dev/null +++ b/environments/production/modules/mysql/templates/my.cnf.erb @@ -0,0 +1,24 @@ +### MANAGED BY PUPPET ### + +<% @options.sort.map do |k,v| -%> +<% if v.is_a?(Hash) -%> +[<%= k %>] +<% v.sort.map do |ki, vi| -%> +<% if ki == 'ssl-disable' or (ki =~ /^ssl/ and v['ssl-disable'] == true) -%> +<% next %> +<% elsif vi == true or vi == '' -%> +<%= ki %> +<% elsif vi.is_a?(Array) -%> +<% vi.each do |vii| -%> +<%= ki %> = <%= vii %> +<% end -%> +<% elsif ![nil, '', :undef].include?(vi) -%> +<%= ki %> = <%= vi %> +<% end -%> +<% end -%> +<% end %> +<% end -%> + +<% if @includedir and @includedir != '' %> +!includedir <%= @includedir %> +<% end %> diff --git a/environments/production/modules/mysql/templates/my.cnf.pass.erb b/environments/production/modules/mysql/templates/my.cnf.pass.erb new file mode 100644 index 0000000..b82cca3 --- /dev/null +++ b/environments/production/modules/mysql/templates/my.cnf.pass.erb @@ -0,0 +1,11 @@ +### MANAGED BY PUPPET ### + +<% %w(mysql client mysqldump mysqladmin mysqlcheck).each do |section| %> +[<%= section -%>] +user=root +host=localhost +<% unless scope.lookupvar('mysql::server::root_password') == 'UNSET' -%> +password='<%= scope.lookupvar('mysql::server::root_password') %>' +<% end -%> +socket=<%= @options['client']['socket'] %> +<% end %> diff --git a/environments/production/modules/mysql/templates/mysqlbackup.sh.erb b/environments/production/modules/mysql/templates/mysqlbackup.sh.erb new file mode 100755 index 0000000..cd193c5 --- /dev/null +++ b/environments/production/modules/mysql/templates/mysqlbackup.sh.erb @@ -0,0 +1,104 @@ +<%- if @kernel == 'Linux' -%> +#!/bin/bash +<%- else -%> +#!/bin/sh +<%- end -%> +# +# MySQL Backup Script +# Dumps mysql databases to a file for another backup tool to pick up. +# +# MySQL code: +# GRANT SELECT, RELOAD, LOCK TABLES ON *.* TO 'user'@'localhost' +# IDENTIFIED BY 'password'; +# FLUSH PRIVILEGES; +# +##### START CONFIG ################################################### + +USER=<%= @backupuser %> +PASS='<%= @backuppassword %>' +MAX_ALLOWED_PACKET=<%= @maxallowedpacket %> +DIR=<%= @backupdir %> +ROTATE=<%= [ Integer(@backuprotate) - 1, 0 ].max %> + +# Create temporary mysql cnf file. +TMPFILE=`mktemp /tmp/backup.XXXXXX` || exit 1 +echo -e "[client]\npassword=$PASS\nuser=$USER\nmax_allowed_packet=$MAX_ALLOWED_PACKET" > $TMPFILE + +# Ensure backup directory exist. +mkdir -p $DIR + +PREFIX=mysql_backup_ +<% if @ignore_events %> +ADDITIONAL_OPTIONS="--ignore-table=mysql.event" +<% else %> +ADDITIONAL_OPTIONS="--events" +<% end %> +<%# Only include routines or triggers if we're doing a file per database -%> +<%# backup. This happens if we named databases, or if we explicitly set -%> +<%# file per database mode -%> +<% if !@backupdatabases.empty? || @file_per_database -%> +<% if @include_triggers -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --triggers" +<% else -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-triggers" +<% end -%> +<% if @include_routines -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --routines" +<% else -%> +ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-routines" +<% end -%> +<% end -%> + +##### STOP CONFIG #################################################### +PATH=<%= @execpath %> + + + +<%- if @kernel == 'Linux' -%> +set -o pipefail +<%- end -%> + +cleanup() +{ + find "${DIR}/" -maxdepth 1 -type f -name "${PREFIX}*.sql*" -mtime +${ROTATE} -print0 | xargs -0 -r rm -f +} + +<% if @delete_before_dump -%> +cleanup + +<% end -%> +<% if @backupdatabases.empty? -%> +<% if @file_per_database -%> +mysql --defaults-extra-file=$TMPFILE -s -r -N -e 'SHOW DATABASES' | while read dbname +do + mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + ${dbname} <% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}${dbname}_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +done +<% else -%> +mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + --all-databases <% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +<% end -%> +<% else -%> +<% @backupdatabases.each do |db| -%> +mysqldump --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ + ${ADDITIONAL_OPTIONS} \ + <%= db %><% if @backupcompress %>| bzcat -zc <% end %>> ${DIR}/${PREFIX}<%= db %>_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %>.bz2<% end %> +<% end -%> +<% end -%> + +<% unless @delete_before_dump -%> +if [ $? -eq 0 ] ; then + cleanup +fi +<% end -%> + +<% if @postscript -%> + <%- [@postscript].flatten.compact.each do |script|%> +<%= script %> + <%- end -%> +<% end -%> + +# Remove temporary file +rm -f $TMPFILE diff --git a/environments/production/modules/mysql/templates/xtrabackup.sh.erb b/environments/production/modules/mysql/templates/xtrabackup.sh.erb new file mode 100644 index 0000000..1449398 --- /dev/null +++ b/environments/production/modules/mysql/templates/xtrabackup.sh.erb @@ -0,0 +1,21 @@ +<%- if @kernel == 'Linux' -%> +#!/bin/bash +<%- else -%> +#!/bin/sh +<%- end -%> +# +# A wrapper for Xtrabackup +# +<% if @prescript -%> + <%- [@prescript].flatten.compact.each do |script| %> +<%= script %> + <%- end -%> +<% end -%> + +innobackupex "$@" + +<% if @postscript -%> + <%- [@postscript].flatten.compact.each do |script| %> +<%= script %> + <%- end -%> +<% end -%> diff --git a/environments/production/modules/nginx/ChangeLog b/environments/production/modules/nginx/ChangeLog new file mode 100644 index 0000000..c569e1a --- /dev/null +++ b/environments/production/modules/nginx/ChangeLog @@ -0,0 +1,53 @@ +2011-06-06 James Fryman + + * Modulefile, README, files/README.markdown, lib/facter/README.markdown, + lib/puppet/parser/functions/README.markdown, + lib/puppet/provider/README.markdown, lib/puppet/type/README.markdown, + manifests/README.markdown, templates/README.markdown, tests/upstream.pp, + tests/vhost.pp, tests/vhost_ssl.erb: Updated Documentation + + * ChangeLog: Updated Changelog + + * lib/facter/ipv6.rb, manifests/config.pp, manifests/init.pp, + manifests/package.pp, manifests/package/suse.pp, manifests/params.pp, + manifests/resource/location.pp, manifests/resource/upstream.pp, + manifests/resource/vhost.pp, manifests/service.pp, manifests/vhost.pp, + templates/conf.d/nginx.conf.erb, templates/conf.d/proxy.conf.erb, + templates/conf.d/upstream.erb, templates/nginx.conf.erb, templates/vhost.erb, + templates/vhost/vhost_footer.erb, templates/vhost/vhost_header.erb, + templates/vhost/vhost_location_directory.erb, + templates/vhost/vhost_location_proxy.erb, + templates/vhost/vhost_ssl_header.erb: Added SuSE Support + + * README, manifests/init.pp, manifests/package.pp, + manifests/package/debian.pp, manifests/package/redhat.pp, manifests/vhost.pp, + templates/vhost.erb: Updated Package Layout + + * lib/facter/ipv6.rb: Added IPV6 Fact Check for Linux distros + +2011-06-02 James Fryman + + * .gitignore, ChangeLog, Modulefile, + files/centos/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL, manifests/config.pp, + manifests/init.pp, manifests/install.pp, manifests/package.pp, + manifests/params.pp, manifests/service.pp, manifests/vhost.pp, metadata.json, + templates/nginx.conf.erb, templates/vhost.erb, templates/vhost.pp: Added + vhost support + +2011-05-24 James Fryman + + * LICENSE, Modulefile, README, files/README.markdown, + files/centos/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL, lib/facter/README.markdown, + lib/puppet/parser/functions/README.markdown, + lib/puppet/provider/README.markdown, lib/puppet/type/README.markdown, + manifests/README.markdown, manifests/config.pp, manifests/init.pp, + manifests/install.pp, manifests/params.pp, manifests/service.pp, + manifests/vhost.pp, metadata.json, spec/README.markdown, spec/spec.opts, + spec/spec_helper.rb, spec/unit/puppet/provider/README.markdown, + spec/unit/puppet/type/README.markdown, templates/README.markdown, + templates/nginx.conf.erb, templates/vhost.pp, tests/init.pp, tests/vhost.pp: + 20110524 Work Snapshot + +2011-05-23 James Fryman + + * : Initial Repository Initilization diff --git a/environments/production/modules/nginx/LICENSE b/environments/production/modules/nginx/LICENSE new file mode 100644 index 0000000..d955a86 --- /dev/null +++ b/environments/production/modules/nginx/LICENSE @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/environments/production/modules/nginx/Modulefile b/environments/production/modules/nginx/Modulefile new file mode 100644 index 0000000..a6f3cbc --- /dev/null +++ b/environments/production/modules/nginx/Modulefile @@ -0,0 +1,10 @@ +name 'puppetlabs-nginx' +version '99.99.99' +source 'http://github.com/puppetlabs/puppetlabs-nginx' +author 'puppetlabs' +license 'Apache License Version 2.0' +summary 'Puppet NGINX management module' +description 'This module can be used for basic NGINX Management' +project_page 'http://github.com/puppetlabs/puppetlabs-nginx' + +dependency 'puppetlabs/stdlib', '>= 0.1.6' diff --git a/environments/production/modules/nginx/README b/environments/production/modules/nginx/README new file mode 100644 index 0000000..e69de29 diff --git a/environments/production/modules/nginx/README.markdown b/environments/production/modules/nginx/README.markdown new file mode 100644 index 0000000..64fa348 --- /dev/null +++ b/environments/production/modules/nginx/README.markdown @@ -0,0 +1,20 @@ +#WARNING + +This module is now unsupported by Puppetlabs. It is replaced by the upstream +version at http://forge.puppetlabs.com/jfryman/nginx which is a much improved +version of this module. It's had many new featured and abilities merged in and +is a true superset of this module. Please adjust your Modulefile's and other +resources to use it instead. + +You can add jfryman's version as a git upstream remote by doing: + +$ git remote add jfryman https://github.com/jfryman/puppet-nginx.git +$ git fetch jfryman + +You can view a list of release tags at: + +https://github.com/jfryman/puppet-nginx/releases + +You can then merge his into yours: + +$ git merge jfryman v0.0.5 diff --git a/environments/production/modules/nginx/manifests/config.pp b/environments/production/modules/nginx/manifests/config.pp new file mode 100644 index 0000000..6545b70 --- /dev/null +++ b/environments/production/modules/nginx/manifests/config.pp @@ -0,0 +1,77 @@ +# Class: nginx::config +# +# This module manages NGINX bootstrap and configuration +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::config( + $worker_processes = $nginx::params::nx_worker_processes, + $worker_connections = $nginx::params::nx_worker_connections, + $proxy_set_header = $nginx::params::nx_proxy_set_header, + $confd_purge = $nginx::params::nx_confd_purge +) inherits nginx::params { + File { + owner => 'root', + group => 'root', + mode => '0644', + } + + file { "${nginx::params::nx_conf_dir}": + ensure => directory, + } + + file { "${nginx::params::nx_conf_dir}/conf.d": + ensure => directory, + } + if $confd_purge == true { + File["${nginx::params::nx_conf_dir}/conf.d"] { + ignore => "vhost_autogen.conf", + purge => true, + recurse => true, + } + } + + + file { "${nginx::config::nx_run_dir}": + ensure => directory, + } + + file { "${nginx::config::nx_client_body_temp_path}": + ensure => directory, + owner => $nginx::params::nx_daemon_user, + } + + file {"${nginx::config::nx_proxy_temp_path}": + ensure => directory, + owner => $nginx::params::nx_daemon_user, + } + + file { '/etc/nginx/sites-enabled/default': + ensure => absent, + } + + file { "${nginx::params::nx_conf_dir}/nginx.conf": + ensure => file, + content => template('nginx/conf.d/nginx.conf.erb'), + } + + file { "${nginx::params::nx_conf_dir}/conf.d/proxy.conf": + ensure => file, + content => template('nginx/conf.d/proxy.conf.erb'), + } + + file { "${nginx::config::nx_temp_dir}/nginx.d": + ensure => directory, + purge => true, + recurse => true, + } +} diff --git a/environments/production/modules/nginx/manifests/init.pp b/environments/production/modules/nginx/manifests/init.pp new file mode 100644 index 0000000..4e4ecea --- /dev/null +++ b/environments/production/modules/nginx/manifests/init.pp @@ -0,0 +1,72 @@ +# Class: nginx +# +# This module manages NGINX. +# +# Parameters: +# +# There are no default parameters for this class. All module parameters are managed +# via the nginx::params class +# +# Actions: +# +# Requires: +# puppetlabs-stdlib - https://github.com/puppetlabs/puppetlabs-stdlib +# +# Packaged NGINX +# - RHEL: EPEL or custom package +# - Debian/Ubuntu: Default Install or custom package +# - SuSE: Default Install or custom package +# +# stdlib +# - puppetlabs-stdlib module >= 0.1.6 +# - plugin sync enabled to obtain the anchor type +# +# Sample Usage: +# +# The module works with sensible defaults: +# +# node default { +# include nginx +# } +class nginx ( + $worker_processes = $nginx::params::nx_worker_processes, + $worker_connections = $nginx::params::nx_worker_connections, + $proxy_set_header = $nginx::params::nx_proxy_set_header, + $confd_purge = $nginx::params::nx_confd_purge, + $configtest_enable = $nginx::params::nx_configtest_enable, + $service_restart = $nginx::params::nx_service_restrart +) inherits nginx::params { + + warning('This module is no longer supported and we direct you to our upstream source, http://forge.puppetlabs.com/jfryman/nginx') + + include stdlib + + class { 'nginx::package': + notify => Class['nginx::service'], + } + + class { 'nginx::config': + worker_processes => $worker_processes, + worker_connections => $worker_connections, + proxy_set_header => $proxy_set_header, + confd_purge => $confd_purge, + require => Class['nginx::package'], + notify => Class['nginx::service'], + } + + class { 'nginx::service': + configtest_enable => $configtest_enable, + service_restart => $service_restart, + } + + # Allow the end user to establish relationships to the "main" class + # and preserve the relationship to the implementation classes through + # a transitive relationship to the composite class. + anchor{ 'nginx::begin': + before => Class['nginx::package'], + notify => Class['nginx::service'], + } + anchor { 'nginx::end': + require => Class['nginx::service'], + } +} diff --git a/environments/production/modules/nginx/manifests/package.pp b/environments/production/modules/nginx/manifests/package.pp new file mode 100644 index 0000000..e0090e1 --- /dev/null +++ b/environments/production/modules/nginx/manifests/package.pp @@ -0,0 +1,40 @@ +# Class: nginx::package +# +# This module manages NGINX package installation +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::package { + anchor { 'nginx::package::begin': } + anchor { 'nginx::package::end': } + + case $::operatingsystem { + centos,fedora,rhel,redhat,scientific: { + class { 'nginx::package::redhat': + require => Anchor['nginx::package::begin'], + before => Anchor['nginx::package::end'], + } + } + debian,ubuntu: { + class { 'nginx::package::debian': + require => Anchor['nginx::package::begin'], + before => Anchor['nginx::package::end'], + } + } + opensuse,suse: { + class { 'nginx::package::suse': + require => Anchor['nginx::package::begin'], + before => Anchor['nginx::package::end'], + } + } + } +} diff --git a/environments/production/modules/nginx/manifests/package/debian.pp b/environments/production/modules/nginx/manifests/package/debian.pp new file mode 100644 index 0000000..760bdb9 --- /dev/null +++ b/environments/production/modules/nginx/manifests/package/debian.pp @@ -0,0 +1,20 @@ +# Class: nginx::package::debian +# +# This module manages NGINX package installation on debian based systems +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::package::debian { + package { 'nginx': + ensure => present, + } +} diff --git a/environments/production/modules/nginx/manifests/package/redhat.pp b/environments/production/modules/nginx/manifests/package/redhat.pp new file mode 100644 index 0000000..f44df2d --- /dev/null +++ b/environments/production/modules/nginx/manifests/package/redhat.pp @@ -0,0 +1,42 @@ +# Class: nginx::package::redhat +# +# This module manages NGINX package installation on RedHat based systems +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::package::redhat { + $redhat_packages = ['nginx', 'GeoIP', 'gd', 'libXpm', 'libxslt'] + + if downcase($::operatingsystem) == "redhat" { + $os_type = "rhel" + } else { + $os_type = downcase($::operatingsystem) + } + + if $::lsbmajdistrelease == undef { + $os_rel = regsubst($::operatingsystemrelease, '\..*$', '') + } else { + $os_rel = $::lsbmajdistrelease + } + + yumrepo { "nginx-release": + baseurl => "http://nginx.org/packages/${os_type}/${os_rel}/\$basearch/", + descr => 'nginx repo', + enabled => '1', + gpgcheck => '0', + } + + package { $redhat_packages: + ensure => present, + require => Yumrepo['nginx-release'], + } +} diff --git a/environments/production/modules/nginx/manifests/package/suse.pp b/environments/production/modules/nginx/manifests/package/suse.pp new file mode 100644 index 0000000..e874848 --- /dev/null +++ b/environments/production/modules/nginx/manifests/package/suse.pp @@ -0,0 +1,29 @@ +# Class: nginx::package::suse +# +# This module manages NGINX package installation for SuSE based systems +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# This module contains all of the required package for SuSE. Apache and all +# other packages listed below are built into the packaged RPM spec for +# SuSE and OpenSuSE. +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::package::suse { + $suse_packages = [ + 'nginx-0.8', 'apache2', 'apache2-itk', 'apache2-utils', 'gd', 'libapr1', + 'libapr-util1', 'libjpeg62', 'libpng14-14', 'libxslt', 'rubygem-daemon_controller', + 'rubygem-fastthread', 'rubygem-file-tail', 'rubygem-passenger', + 'rubygem-passenger-nginx', 'rubygem-rack', 'rubygem-rake', 'rubygem-spruz', + ] + + package { $suse_packages: + ensure => present, + } +} diff --git a/environments/production/modules/nginx/manifests/params.pp b/environments/production/modules/nginx/manifests/params.pp new file mode 100644 index 0000000..dfdadfb --- /dev/null +++ b/environments/production/modules/nginx/manifests/params.pp @@ -0,0 +1,64 @@ +# Class: nginx::params +# +# This module manages NGINX paramaters +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::params { + $nx_temp_dir = '/tmp' + $nx_run_dir = '/var/nginx' + + $nx_conf_dir = '/etc/nginx' + $nx_confd_purge = false + $nx_worker_processes = 1 + $nx_worker_connections = 1024 + $nx_multi_accept = off + $nx_sendfile = on + $nx_keepalive_timeout = 65 + $nx_tcp_nodelay = on + $nx_gzip = on + + $nx_proxy_redirect = off + $nx_proxy_set_header = [ + 'Host $host', 'X-Real-IP $remote_addr', + 'X-Forwarded-For $proxy_add_x_forwarded_for', + ] + + $nx_client_body_temp_path = "${nx_run_dir}/client_body_temp" + $nx_client_body_buffer_size = '128k' + $nx_client_max_body_size = '10m' + $nx_proxy_temp_path = "${nx_run_dir}/proxy_temp" + $nx_proxy_connect_timeout = '90' + $nx_proxy_send_timeout = '90' + $nx_proxy_read_timeout = '90' + $nx_proxy_buffers = '32 4k' + + $nx_logdir = $::kernel ? { + /(?i-mx:linux)/ => '/var/log/nginx', + } + + $nx_pid = $::kernel ? { + /(?i-mx:linux)/ => '/var/run/nginx.pid', + } + + $nx_daemon_user = $::operatingsystem ? { + /(?i-mx:debian|ubuntu)/ => 'www-data', + /(?i-mx:fedora|rhel|redhat|centos|scientific|suse|opensuse)/ => 'nginx', + } + + # Service restart after Nginx 0.7.53 could also be just "/path/to/nginx/bin -s HUP" + # Some init scripts do a configtest, some don't. If configtest_enable it's true + # then service restart will take $nx_service_restart value, forcing configtest. + $nx_configtest_enable = false + $nx_service_restart = "/etc/init.d/nginx configtest && /etc/init.d/nginx restart" + +} diff --git a/environments/production/modules/nginx/manifests/resource/location.pp b/environments/production/modules/nginx/manifests/resource/location.pp new file mode 100644 index 0000000..8949f90 --- /dev/null +++ b/environments/production/modules/nginx/manifests/resource/location.pp @@ -0,0 +1,117 @@ +# define: nginx::resource::location +# +# This definition creates a new location entry within a virtual host +# +# Parameters: +# [*ensure*] - Enables or disables the specified location (present|absent) +# [*vhost*] - Defines the default vHost for this location entry to include with +# [*location*] - Specifies the URI associated with this location entry +# [*www_root*] - Specifies the location on disk for files to be read from. Cannot be set in conjunction with $proxy +# [*index_files*] - Default index files for NGINX to read when traversing a directory +# [*proxy*] - Proxy server(s) for a location to connect to. Accepts a single value, can be used in conjunction +# with nginx::resource::upstream +# [*proxy_read_timeout*] - Override the default the proxy read timeout value of 90 seconds +# [*ssl*] - Indicates whether to setup SSL bindings for this location. +# [*ssl_only*] - Required if the SSL and normal vHost have the same port. +# [*location_alias*] - Path to be used as basis for serving requests for this location +# [*stub_status*] - If true it will point configure module stub_status to provide nginx stats on location +# [*location_cfg_prepend*] - It expects a hash with custom directives to put before anything else inside location +# [*location_cfg_append*] - It expects a hash with custom directives to put after everything else inside location +# [*try_files*] - An array of file locations to try +# [*option*] - Reserved for future use +# +# Actions: +# +# Requires: +# +# Sample Usage: +# nginx::resource::location { 'test2.local-bob': +# ensure => present, +# www_root => '/var/www/bob', +# location => '/bob', +# vhost => 'test2.local', +# } +# +# Custom config example to limit location on localhost, +# create a hash with any extra custom config you want. +# $my_config = { +# 'access_log' => 'off', +# 'allow' => '127.0.0.1', +# 'deny' => 'all' +# } +# nginx::resource::location { 'test2.local-bob': +# ensure => present, +# www_root => '/var/www/bob', +# location => '/bob', +# vhost => 'test2.local', +# location_cfg_append => $my_config, +# } + +define nginx::resource::location( + $ensure = present, + $vhost = undef, + $www_root = undef, + $index_files = ['index.html', 'index.htm', 'index.php'], + $proxy = undef, + $proxy_read_timeout = $nginx::params::nx_proxy_read_timeout, + $ssl = false, + $ssl_only = false, + $location_alias = undef, + $option = undef, + $stub_status = undef, + $location_cfg_prepend = undef, + $location_cfg_append = undef, + $try_files = undef, + $location +) { + File { + owner => 'root', + group => 'root', + mode => '0644', + notify => Class['nginx::service'], + } + + ## Shared Variables + $ensure_real = $ensure ? { + 'absent' => absent, + default => file, + } + + # Use proxy template if $proxy is defined, otherwise use directory template. + if ($proxy != undef) { + $content_real = template('nginx/vhost/vhost_location_proxy.erb') + } elsif ($location_alias != undef) { + $content_real = template('nginx/vhost/vhost_location_alias.erb') + } elsif ($stub_status != undef) { + $content_real = template('nginx/vhost/vhost_location_stub_status.erb') + } else { + $content_real = template('nginx/vhost/vhost_location_directory.erb') + } + + ## Check for various error condtiions + if ($vhost == undef) { + fail('Cannot create a location reference without attaching to a virtual host') + } + if (($www_root == undef) and ($proxy == undef) and ($location_alias == undef) and ($stub_status == undef) ) { + fail('Cannot create a location reference without a www_root, proxy, location_alias or stub_status defined') + } + if (($www_root != undef) and ($proxy != undef)) { + fail('Cannot define both directory and proxy in a virtual host') + } + + ## Create stubs for vHost File Fragment Pattern + if ($ssl_only != 'true') { + file {"${nginx::config::nx_temp_dir}/nginx.d/${vhost}-500-${name}": + ensure => $ensure_real, + content => $content_real, + } + } + + ## Only create SSL Specific locations if $ssl is true. + if ($ssl == 'true') { + file {"${nginx::config::nx_temp_dir}/nginx.d/${vhost}-800-${name}-ssl": + ensure => $ensure_real, + content => $content_real, + } + } +} diff --git a/environments/production/modules/nginx/manifests/resource/upstream.pp b/environments/production/modules/nginx/manifests/resource/upstream.pp new file mode 100644 index 0000000..acb0427 --- /dev/null +++ b/environments/production/modules/nginx/manifests/resource/upstream.pp @@ -0,0 +1,40 @@ +# define: nginx::resource::upstream +# +# This definition creates a new upstream proxy entry for NGINX +# +# Parameters: +# [*ensure*] - Enables or disables the specified location (present|absent) +# [*members*] - Array of member URIs for NGINX to connect to. Must follow valid NGINX syntax. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# nginx::resource::upstream { 'proxypass': +# ensure => present, +# members => [ +# 'localhost:3000', +# 'localhost:3001', +# 'localhost:3002', +# ], +# } +define nginx::resource::upstream ( + $ensure = 'present', + $members +) { + File { + owner => 'root', + group => 'root', + mode => '0644', + } + + file { "/etc/nginx/conf.d/${name}-upstream.conf": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/conf.d/upstream.erb'), + notify => Class['nginx::service'], + } +} diff --git a/environments/production/modules/nginx/manifests/resource/vhost.pp b/environments/production/modules/nginx/manifests/resource/vhost.pp new file mode 100644 index 0000000..8b29172 --- /dev/null +++ b/environments/production/modules/nginx/manifests/resource/vhost.pp @@ -0,0 +1,158 @@ +# define: nginx::resource::vhost +# +# This definition creates a virtual host +# +# Parameters: +# [*ensure*] - Enables or disables the specified vhost (present|absent) +# [*listen_ip*] - Default IP Address for NGINX to listen with this vHost on. Defaults to all interfaces (*) +# [*listen_port*] - Default IP Port for NGINX to listen with this vHost on. Defaults to TCP 80 +# [*listen_options*] - Extra options for listen directive like 'default' to catchall. Undef by default. +# [*ipv6_enable*] - BOOL value to enable/disable IPv6 support (false|true). Module will check to see if IPv6 +# support exists on your system before enabling. +# [*ipv6_listen_ip*] - Default IPv6 Address for NGINX to listen with this vHost on. Defaults to all interfaces (::) +# [*ipv6_listen_port*] - Default IPv6 Port for NGINX to listen with this vHost on. Defaults to TCP 80 +# [*ipv6_listen_options*] - Extra options for listen directive like 'default' to catchall. Template will allways add ipv6only=on. +# While issue jfryman/puppet-nginx#30 is discussed, default value is 'default'. +# [*index_files*] - Default index files for NGINX to read when traversing a directory +# [*proxy*] - Proxy server(s) for the root location to connect to. Accepts a single value, can be used in +# conjunction with nginx::resource::upstream +# [*proxy_read_timeout*] - Override the default the proxy read timeout value of 90 seconds +# [*ssl*] - Indicates whether to setup SSL bindings for this vhost. +# [*ssl_cert*] - Pre-generated SSL Certificate file to reference for SSL Support. This is not generated by this module. +# [*ssl_key*] - Pre-generated SSL Key file to reference for SSL Support. This is not generated by this module. +# [*ssl_port*] - Default IP Port for NGINX to listen with this SSL vHost on. Defaults to TCP 443 +# [*server_name*] - List of vhostnames for which this vhost will respond. Default [$name]. +# [*www_root*] - Specifies the location on disk for files to be read from. Cannot be set in conjunction with $proxy +# [*rewrite_www_to_non_www*] - Adds a server directive and rewrite rule to rewrite www.domain.com to domain.com in order to avoid +# duplicate content (SEO); +# [*try_files*] - Specifies the locations for files to be checked as an array. Cannot be used in conjuction with $proxy. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# nginx::resource::vhost { 'test2.local': +# ensure => present, +# www_root => '/var/www/nginx-default', +# ssl => 'true', +# ssl_cert => '/tmp/server.crt', +# ssl_key => '/tmp/server.pem', +# } +define nginx::resource::vhost( + $ensure = 'enable', + $listen_ip = '*', + $listen_port = '80', + $listen_options = undef, + $ipv6_enable = false, + $ipv6_listen_ip = '::', + $ipv6_listen_port = '80', + $ipv6_listen_options = 'default', + $ssl = false, + $ssl_cert = undef, + $ssl_key = undef, + $ssl_port = '443', + $proxy = undef, + $proxy_read_timeout = $nginx::params::nx_proxy_read_timeout, + $index_files = ['index.html', 'index.htm', 'index.php'], + $server_name = [$name], + $www_root = undef, + $rewrite_www_to_non_www = false, + $location_cfg_prepend = undef, + $location_cfg_append = undef, + $try_files = undef +) { + + File { + owner => 'root', + group => 'root', + mode => '0644', + } + + # Add IPv6 Logic Check - Nginx service will not start if ipv6 is enabled + # and support does not exist for it in the kernel. + if ($ipv6_enable == 'true') and ($ipaddress6) { + warning('nginx: IPv6 support is not enabled or configured properly') + } + + # Check to see if SSL Certificates are properly defined. + if ($ssl == 'true') { + if ($ssl_cert == undef) or ($ssl_key == undef) { + fail('nginx: SSL certificate/key (ssl_cert/ssl_cert) and/or SSL Private must be defined and exist on the target system(s)') + } + } + + # Use the File Fragment Pattern to construct the configuration files. + # Create the base configuration file reference. + if ($listen_port != $ssl_port) { + file { "${nginx::config::nx_temp_dir}/nginx.d/${name}-001": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/vhost/vhost_header.erb'), + notify => Class['nginx::service'], + } + } + + if ($ssl == 'true') and ($ssl_port == $listen_port) { + $ssl_only = 'true' + } + + # Create the default location reference for the vHost + nginx::resource::location {"${name}-default": + ensure => $ensure, + vhost => $name, + ssl => $ssl, + ssl_only => $ssl_only, + location => '/', + proxy => $proxy, + proxy_read_timeout => $proxy_read_timeout, + try_files => $try_files, + www_root => $www_root, + notify => Class['nginx::service'], + } + + # Support location_cfg_prepend and location_cfg_append on default location created by vhost + if $location_cfg_prepend { + Nginx::Resource::Location["${name}-default"] { + location_cfg_prepend => $location_cfg_prepend + } + } + if $location_cfg_append { + Nginx::Resource::Location["${name}-default"] { + location_cfg_append => $location_cfg_append + } + } + # Create a proper file close stub. + if ($listen_port != $ssl_port) { + file { "${nginx::config::nx_temp_dir}/nginx.d/${name}-699": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/vhost/vhost_footer.erb'), + notify => Class['nginx::service'], + } + } + + # Create SSL File Stubs if SSL is enabled + if ($ssl == 'true') { + file { "${nginx::config::nx_temp_dir}/nginx.d/${name}-700-ssl": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/vhost/vhost_ssl_header.erb'), + notify => Class['nginx::service'], + } + file { "${nginx::config::nx_temp_dir}/nginx.d/${name}-999-ssl": + ensure => $ensure ? { + 'absent' => absent, + default => 'file', + }, + content => template('nginx/vhost/vhost_footer.erb'), + notify => Class['nginx::service'], + } + } +} diff --git a/environments/production/modules/nginx/manifests/service.pp b/environments/production/modules/nginx/manifests/service.pp new file mode 100644 index 0000000..16a4005 --- /dev/null +++ b/environments/production/modules/nginx/manifests/service.pp @@ -0,0 +1,38 @@ +# Class: nginx::service +# +# This module manages NGINX service management and vhost rebuild +# +# Parameters: +# +# There are no default parameters for this class. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +# This class file is not called directly +class nginx::service( + $configtest_enable = $nginx::params::nx_configtest_enable, + $service_restart = $nginx::params::nx_service_restart +) { + exec { 'rebuild-nginx-vhosts': + command => "/bin/cat ${nginx::params::nx_temp_dir}/nginx.d/* > ${nginx::params::nx_conf_dir}/conf.d/vhost_autogen.conf", + refreshonly => true, + unless => "/usr/bin/test ! -f ${nginx::params::nx_temp_dir}/nginx.d/*", + subscribe => File["${nginx::params::nx_temp_dir}/nginx.d"], + } + service { "nginx": + ensure => running, + enable => true, + hasstatus => true, + hasrestart => true, + subscribe => Exec['rebuild-nginx-vhosts'], + } + if $configtest_enable == true { + Service["nginx"] { + restart => $service_restart, + } + } +} diff --git a/environments/production/modules/nginx/metadata.json b/environments/production/modules/nginx/metadata.json new file mode 100644 index 0000000..7fa8403 --- /dev/null +++ b/environments/production/modules/nginx/metadata.json @@ -0,0 +1,52 @@ +{ + "name": "puppetlabs-nginx", + "version": "99.99.99", + "source": "http://github.com/puppetlabs/puppetlabs-nginx", + "author": "puppetlabs", + "license": "Apache License Version 2.0", + "summary": "Puppet NGINX management module", + "description": "This module can be used for basic NGINX Management", + "project_page": "http://github.com/puppetlabs/puppetlabs-nginx", + "dependencies": [ + { + "name": "puppetlabs/stdlib", + "version_requirement": ">= 0.1.6" + } + ], + "types": [ + + ], + "checksums": { + "ChangeLog": "92cb1e2fac43c0e6f733ee8a8384bd2f", + "LICENSE": "86a0725dcd00b87b9929258039db566c", + "Modulefile": "a53ce3a1ce43efb4715d0cf3fefc8099", + "README": "d41d8cd98f00b204e9800998ecf8427e", + "README.markdown": "ace6eae1ba085fcd36819fcfc340782f", + "manifests/config.pp": "4adcfa0dcf159f289773e056d47e5ba2", + "manifests/init.pp": "933e0d0a68e702c3625af26d4c40d5d0", + "manifests/package/debian.pp": "e64807f6b6f28545bebec75d73e9714d", + "manifests/package/redhat.pp": "70a70aec1b652b90a5f3cc64adbb809e", + "manifests/package/suse.pp": "79082f365f817b3e708ef058af360e32", + "manifests/package.pp": "93262701c8815d0f6c1df007f2171a39", + "manifests/params.pp": "d49d3d548acd4d7ecaaacaa6ff9d966a", + "manifests/resource/location.pp": "c4298ab56d455f6a8f7ef73ce4d0728c", + "manifests/resource/upstream.pp": "21f63a855573e79b6384b637ab753b3c", + "manifests/resource/vhost.pp": "6f535b2e46b353dd124c9010b05148fc", + "manifests/service.pp": "0b9dc6851195b454382a6d0c5a90603e", + "templates/conf.d/nginx.conf.erb": "15c2344995a56945a437b79abc161ad2", + "templates/conf.d/proxy.conf.erb": "15287f6cabdecf173cff6cd72e535d81", + "templates/conf.d/upstream.erb": "65e541f63df1d566c2f3628282c7f29e", + "templates/vhost/vhost_footer.erb": "dad515035220165ad0f460ce7ec2e29a", + "templates/vhost/vhost_header.erb": "028c74276f634c5c23e1b933b1132bcb", + "templates/vhost/vhost_location_alias.erb": "86749cb78ba503522e90d6db88a0b8c5", + "templates/vhost/vhost_location_directory.erb": "2cd577a9b5edf05f469d0bc7ca2b8807", + "templates/vhost/vhost_location_proxy.erb": "988729b42f82b452bedff0fd66a0066c", + "templates/vhost/vhost_location_stub_status.erb": "98f38c02a314179217be883319fbed41", + "templates/vhost/vhost_ssl_header.erb": "19fc7ca77406e36301d044f19ab1b3ce", + "tests/init.pp": "65966654dd24c431365d66239b8bc8c8", + "tests/location_alias.pp": "c0b6f6b2f8a655cbc90faf735b05bde0", + "tests/upstream.pp": "c64fcbcb7b449d412b49cfb10a1297b6", + "tests/vhost.pp": "5a8a115f85f50a462f08882a4e028405", + "tests/vhost_ssl.erb": "06d4999eec451008c0a956f41088e642" + } +} \ No newline at end of file diff --git a/environments/production/modules/nginx/templates/conf.d/nginx.conf.erb b/environments/production/modules/nginx/templates/conf.d/nginx.conf.erb new file mode 100644 index 0000000..42d254c --- /dev/null +++ b/environments/production/modules/nginx/templates/conf.d/nginx.conf.erb @@ -0,0 +1,33 @@ +user <%= scope.lookupvar('nginx::config::nx_daemon_user') %>; +worker_processes <%= worker_processes %>; + +error_log <%= scope.lookupvar('nginx::params::nx_logdir')%>/error.log; +pid <%= scope.lookupvar('nginx::params::nx_pid')%>; + +events { + worker_connections <%= worker_connections %>; + <% if scope.lookupvar('nginx::params::nx_multi_accept') == 'on' %>multi_accept on;<% end %> +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log <%= scope.lookupvar('nginx::params::nx_logdir')%>/access.log; + + sendfile <%= scope.lookupvar('nginx::params::nx_sendfile')%>; + + <% if scope.lookupvar('nginx::params::nx_tcp_nopush') == 'on' %> + tcp_nopush on; + <% end %> + + keepalive_timeout <%= scope.lookupvar('nginx::params::nx_keepalive_timeout')%>; + tcp_nodelay <%= scope.lookupvar('nginx::params::nx_tcp_nodelay')%>; + + <% if scope.lookupvar('nginx::params::nx_gzip') == 'on' %> + gzip on; + gzip_disable "MSIE [1-6]\.(?!.*SV1)"; + <% end %> + + include /etc/nginx/conf.d/*.conf; +} diff --git a/environments/production/modules/nginx/templates/conf.d/proxy.conf.erb b/environments/production/modules/nginx/templates/conf.d/proxy.conf.erb new file mode 100644 index 0000000..b05d71d --- /dev/null +++ b/environments/production/modules/nginx/templates/conf.d/proxy.conf.erb @@ -0,0 +1,10 @@ +proxy_redirect <%= scope.lookupvar('nginx::params::nx_proxy_redirect') %>; +client_max_body_size <%= scope.lookupvar('nginx::params::nx_client_max_body_size') %>; +client_body_buffer_size <%= scope.lookupvar('nginx::params::nx_client_body_buffer_size') %>; +proxy_connect_timeout <%= scope.lookupvar('nginx::params::nx_proxy_connect_timeout') %>; +proxy_send_timeout <%= scope.lookupvar('nginx::params::nx_proxy_send_timeout') %>; +proxy_read_timeout <%= scope.lookupvar('nginx::params::nx_proxy_read_timeout') %>; +proxy_buffers <%= scope.lookupvar('nginx::params::nx_proxy_buffers') %>; +<% proxy_set_header.each do |header| %> +proxy_set_header <%= header %>; +<% end %> diff --git a/environments/production/modules/nginx/templates/conf.d/upstream.erb b/environments/production/modules/nginx/templates/conf.d/upstream.erb new file mode 100644 index 0000000..4180a87 --- /dev/null +++ b/environments/production/modules/nginx/templates/conf.d/upstream.erb @@ -0,0 +1,5 @@ +upstream <%= name %> { + <% members.each do |i| %> + server <%= i %>; + <% end %> +} diff --git a/environments/production/modules/nginx/templates/vhost/vhost_footer.erb b/environments/production/modules/nginx/templates/vhost/vhost_footer.erb new file mode 100644 index 0000000..1d2f0c3 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_footer.erb @@ -0,0 +1,9 @@ +} + +<% if rewrite_www_to_non_www %> +server { + listen <%= listen_ip %>; + server_name www.<%= name.gsub(/^www\./, '') %>; + rewrite ^ http://<%= name.gsub(/^www\./, '') %>$uri permanent; +} +<% end %> diff --git a/environments/production/modules/nginx/templates/vhost/vhost_header.erb b/environments/production/modules/nginx/templates/vhost/vhost_header.erb new file mode 100644 index 0000000..22f7e06 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_header.erb @@ -0,0 +1,8 @@ +server { + listen <%= listen_ip %>:<%= listen_port %> <% if @listen_options %><%= listen_options %><% end %>; + <% # check to see if ipv6 support exists in the kernel before applying %> + <% if ipv6_enable == 'true' && (defined? ipaddress6) %> + listen [<%= ipv6_listen_ip %>]:<%= ipv6_listen_port %> <% if @ipv6_listen_options %><%= ipv6_listen_options %><% end %> ipv6only=on; + <% end %> + server_name <%= rewrite_www_to_non_www ? name.gsub(/^www\./, '') : server_name.join(" ") %>; + access_log <%= scope.lookupvar('nginx::params::nx_logdir')%>/<%= name %>.access.log; diff --git a/environments/production/modules/nginx/templates/vhost/vhost_location_alias.erb b/environments/production/modules/nginx/templates/vhost/vhost_location_alias.erb new file mode 100644 index 0000000..75d57c1 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_location_alias.erb @@ -0,0 +1,9 @@ + location <%= location %> { +<% if @location_cfg_prepend -%><% location_cfg_prepend.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + alias <%= location_alias %>; +<% if @location_cfg_append -%><% location_cfg_append.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + } diff --git a/environments/production/modules/nginx/templates/vhost/vhost_location_directory.erb b/environments/production/modules/nginx/templates/vhost/vhost_location_directory.erb new file mode 100644 index 0000000..f73fa11 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_location_directory.erb @@ -0,0 +1,13 @@ + location <%= location %> { +<% if @location_cfg_prepend -%><% location_cfg_prepend.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + root <%= www_root %>; + <% if has_variable?("try_files") then %> + try_files <% try_files.each do |try| -%> <%= try %> <% end -%>; + <% end %> + index <% index_files.each do |i| %> <%= i %> <% end %>; +<% if @location_cfg_append -%><% location_cfg_append.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + } diff --git a/environments/production/modules/nginx/templates/vhost/vhost_location_proxy.erb b/environments/production/modules/nginx/templates/vhost/vhost_location_proxy.erb new file mode 100644 index 0000000..30711a2 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_location_proxy.erb @@ -0,0 +1,10 @@ + location <%= location %> { +<% if @location_cfg_prepend -%><% location_cfg_prepend.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + proxy_pass <%= proxy %>; + proxy_read_timeout <%= proxy_read_timeout %>; +<% if @location_cfg_append -%><% location_cfg_append.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + } diff --git a/environments/production/modules/nginx/templates/vhost/vhost_location_stub_status.erb b/environments/production/modules/nginx/templates/vhost/vhost_location_stub_status.erb new file mode 100644 index 0000000..70b9727 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_location_stub_status.erb @@ -0,0 +1,9 @@ + location <%= location %> { +<% if @location_cfg_prepend -%><% location_cfg_prepend.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + stub_status on; +<% if @location_cfg_append -%><% location_cfg_append.each do |key,value| -%> + <%= key %> <%= value %>; +<% end -%><% end -%> + } diff --git a/environments/production/modules/nginx/templates/vhost/vhost_ssl_header.erb b/environments/production/modules/nginx/templates/vhost/vhost_ssl_header.erb new file mode 100644 index 0000000..fc17e88 --- /dev/null +++ b/environments/production/modules/nginx/templates/vhost/vhost_ssl_header.erb @@ -0,0 +1,16 @@ +server { + listen <%= ssl_port %>; + <% if ipv6_enable == 'true' && (defined? ipaddress6) %> + listen [<%= ipv6_listen_ip %>]:<%= ipv6_listen_port %> <% if @ipv6_listen_options %><%= ipv6_listen_options %><% end %> ipv6only=on; + <% end %> + server_name <%= rewrite_www_to_non_www ? name.gsub(/^www\./, '') : server_name.join(" ") %>; + + ssl on; + ssl_certificate <%= ssl_cert %>; + ssl_certificate_key <%= ssl_key %>; + + ssl_session_timeout 5m; + + ssl_protocols SSLv3 TLSv1; + ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; + ssl_prefer_server_ciphers on; diff --git a/environments/production/modules/nginx/tests/init.pp b/environments/production/modules/nginx/tests/init.pp new file mode 100644 index 0000000..41cc19d --- /dev/null +++ b/environments/production/modules/nginx/tests/init.pp @@ -0,0 +1,9 @@ +# The notify before should always come BEFORE all resources +# managed by the nginx class +# and the notify last should always come AFTER all resources +# managed by the nginx class. +node default { + notify { 'before': } + -> class { 'nginx': } + -> notify { 'last': } +} diff --git a/environments/production/modules/nginx/tests/location_alias.pp b/environments/production/modules/nginx/tests/location_alias.pp new file mode 100644 index 0000000..ac774ba --- /dev/null +++ b/environments/production/modules/nginx/tests/location_alias.pp @@ -0,0 +1,8 @@ +include nginx + +nginx::resource::location { 'www.test.com-alias': + ensure => present, + location => '/some/url', + location_alias => '/new/url/', + vhost => 'www.test.com', +} diff --git a/environments/production/modules/nginx/tests/upstream.pp b/environments/production/modules/nginx/tests/upstream.pp new file mode 100644 index 0000000..8cc323b --- /dev/null +++ b/environments/production/modules/nginx/tests/upstream.pp @@ -0,0 +1,10 @@ +include nginx + +nginx::resource::upstream { 'proxypass': + ensure => present, + members => [ + 'localhost:3000', + 'localhost:3001', + 'localhost:3002', + ], +} diff --git a/environments/production/modules/nginx/tests/vhost.pp b/environments/production/modules/nginx/tests/vhost.pp new file mode 100644 index 0000000..78b050b --- /dev/null +++ b/environments/production/modules/nginx/tests/vhost.pp @@ -0,0 +1,16 @@ +include nginx + +nginx::resource::vhost { 'test.local': + ensure => present, + ipv6_enable => 'true', + proxy => 'http://proxypass', +} + +nginx::resource::vhost { 'test.local:8080': + listen_port => 8080, + server_name => 'test.local', + ensure => present, + ipv6_enable => 'true', + proxy => 'http://proxypass', +} + diff --git a/environments/production/modules/nginx/tests/vhost_ssl.erb b/environments/production/modules/nginx/tests/vhost_ssl.erb new file mode 100644 index 0000000..30e8bfb --- /dev/null +++ b/environments/production/modules/nginx/tests/vhost_ssl.erb @@ -0,0 +1,17 @@ +include nginx + +nginx::resource::vhost { 'test2.local': + ensure => present, + www_root => '/var/www/nginx-default', + ssl => 'true', + ssl_cert => '/tmp/server.crt', + ssl_key => '/tmp/server.pem', +} + +nginx::resource::location { 'test2.local-bob': + ensure => present, + www_root => '/var/www/bob', + location => '/bob', + vhost => 'test2.local', +} + diff --git a/environments/production/modules/staging/CHANGELOG.md b/environments/production/modules/staging/CHANGELOG.md new file mode 100644 index 0000000..1bdfeef --- /dev/null +++ b/environments/production/modules/staging/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 2017-02-11 - Release 2.2.0 + +This is the last release with Puppet3 support! +* Modulesync + +## 2016-12-25 - Release 2.1.0 + +* Modulesync with latest Vox Pupuli defaults +* Ability to manage file attributes on target +* Strip trailing slashes off paths +* Improve spec tests + +## 2016-07-09 - Release 2.0.1 + +* Fix some typos/spelling +* modulesync with latest Vox Pupuli defaults + +## 2016-06-10 - Release 2.0.0 + +* Drop of Ruby1.8 support! +* modulesync to latest voxpupli configs +* Add support for OpenBSD +* Allow to pass in the full path to a file instead of specifying the file name explictly +* Revocation of 1.0.6 (wrong semver) + +## 2016-03-20 - Release 1.0.6 + +* Publish the first version of this module in the puppet namespace. diff --git a/environments/production/modules/staging/CONTRIBUTING.md b/environments/production/modules/staging/CONTRIBUTING.md new file mode 100644 index 0000000..8cac3bd --- /dev/null +++ b/environments/production/modules/staging/CONTRIBUTING.md @@ -0,0 +1,97 @@ +This module has grown over time based on a range of contributions from +people using it. If you follow these contributing guidelines your patch +will likely make it into a release a little quicker. + + +## Contributing + +Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. [Contributor Code of Conduct](https://voxpupuli.org/coc/). + +1. Fork the repo. + +1. Create a separate branch for your change. + +1. Run the tests. We only take pull requests with passing tests, and + documentation. + +1. Add a test for your change. Only refactoring and documentation + changes require no new tests. If you are adding functionality + or fixing a bug, please add a test. + +1. Squash your commits down into logical components. Make sure to rebase + against the current master. + +1. Push the branch to your fork and submit a pull request. + +Please be prepared to repeat some of these steps as our contributors review +your code. + +## Dependencies + +The testing and development tools have a bunch of dependencies, +all managed by [bundler](http://bundler.io/) according to the +[Puppet support matrix](http://docs.puppetlabs.com/guides/platforms.html#ruby-versions). + +By default the tests use a baseline version of Puppet. + +If you have Ruby 2.x or want a specific version of Puppet, +you must set an environment variable such as: + + export PUPPET_VERSION="~> 4.2.0" + +Install the dependencies like so... + + bundle install + +## Syntax and style + +The test suite will run [Puppet Lint](http://puppet-lint.com/) and +[Puppet Syntax](https://github.com/gds-operations/puppet-syntax) to +check various syntax and style things. You can run these locally with: + + bundle exec rake lint + bundle exec rake validate + +## Running the unit tests + +The unit test suite covers most of the code, as mentioned above please +add tests if you're adding new functionality. If you've not used +[rspec-puppet](http://rspec-puppet.com/) before then feel free to ask +about how best to test your new feature. + +To run your all the unit tests + + bundle exec rake spec SPEC_OPTS='--format documentation' + +To run a specific spec test set the `SPEC` variable: + + bundle exec rake spec SPEC=spec/foo_spec.rb + +To run the linter, the syntax checker and the unit tests: + + bundle exec rake test + + +## Integration tests + +The unit tests just check the code runs, not that it does exactly what +we want on a real machine. For that we're using +[beaker](https://github.com/puppetlabs/beaker). + +This fires up a new virtual machine (using vagrant) and runs a series of +simple tests against it after applying the module. You can run this +with: + + bundle exec rake acceptance + +This will run the tests on an Ubuntu 12.04 virtual machine. You can also +run the integration tests against Centos 6.5 with. + + BEAKER_set=centos-64-x64 bundle exec rake acceptances + +If you don't want to have to recreate the virtual machine every time you +can use `BEAKER_DESTROY=no` and `BEAKER_PROVISION=no`. On the first run you will +at least need `BEAKER_PROVISION` set to yes (the default). The Vagrantfile +for the created virtual machines will be in `.vagrant/beaker_vagrant_fies`. + +# vim: syntax=markdown diff --git a/environments/production/modules/staging/Gemfile b/environments/production/modules/staging/Gemfile new file mode 100644 index 0000000..e8db724 --- /dev/null +++ b/environments/production/modules/staging/Gemfile @@ -0,0 +1,68 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +def location_for(place, fake_version = nil) + if place =~ /^(git[:@][^#]*)#(.*)/ + [fake_version, { :git => $1, :branch => $2, :require => false }].compact + elsif place =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [place, { :require => false }] + end +end + +group :test do + gem 'puppetlabs_spec_helper', '~> 1.2.2', :require => false + gem 'rspec-puppet', '~> 2.5', :require => false + gem 'rspec-puppet-facts', :require => false + gem 'rspec-puppet-utils', :require => false + gem 'puppet-lint-absolute_classname-check', :require => false + gem 'puppet-lint-leading_zero-check', :require => false + gem 'puppet-lint-trailing_comma-check', :require => false + gem 'puppet-lint-version_comparison-check', :require => false + gem 'puppet-lint-classes_and_types_beginning_with_digits-check', :require => false + gem 'puppet-lint-unquoted_string-check', :require => false + gem 'puppet-lint-variable_contains_upcase', :require => false + gem 'metadata-json-lint', :require => false + gem 'puppet-blacksmith', :require => false + gem 'voxpupuli-release', :require => false, :git => 'https://github.com/voxpupuli/voxpupuli-release-gem.git' + gem 'puppet-strings', '~> 1.0.0', :require => false + gem 'redcarpet', :require => false + gem 'rubocop', '~> 0.47.0', :require => false if RUBY_VERSION >= '2.3.0' + gem 'rubocop-rspec', '~> 1.10.0', :require => false if RUBY_VERSION >= '2.3.0' + gem 'json_pure', '<= 2.0.1', :require => false if RUBY_VERSION < '2.0.0' + gem 'mocha', '>= 1.2.1', :require => false + gem 'coveralls', :require => false if RUBY_VERSION >= '2.0.0' + gem 'simplecov-console', :require => false if RUBY_VERSION >= '2.0.0' +end + +group :development do + gem 'travis', :require => false + gem 'travis-lint', :require => false + gem 'guard-rake', :require => false +end + +group :system_tests do + if beaker_version = ENV['BEAKER_VERSION'] + gem 'beaker', *location_for(beaker_version) + end + if beaker_rspec_version = ENV['BEAKER_RSPEC_VERSION'] + gem 'beaker-rspec', *location_for(beaker_rspec_version) + else + gem 'beaker-rspec', :require => false + end + gem 'serverspec', :require => false + gem 'beaker-puppet_install_helper', :require => false +end + + + +if facterversion = ENV['FACTER_GEM_VERSION'] + gem 'facter', facterversion.to_s, :require => false, :groups => [:test] +else + gem 'facter', :require => false, :groups => [:test] +end + +ENV['PUPPET_VERSION'].nil? ? puppetversion = '~> 4.0' : puppetversion = ENV['PUPPET_VERSION'].to_s +gem 'puppet', puppetversion, :require => false, :groups => [:test] + +# vim: syntax=ruby diff --git a/environments/production/modules/staging/LICENSE b/environments/production/modules/staging/LICENSE new file mode 100644 index 0000000..51fca54 --- /dev/null +++ b/environments/production/modules/staging/LICENSE @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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/environments/production/modules/staging/README.md b/environments/production/modules/staging/README.md new file mode 100644 index 0000000..68a1495 --- /dev/null +++ b/environments/production/modules/staging/README.md @@ -0,0 +1,79 @@ +# Staging module for Puppet + +[![Build Status](https://travis-ci.org/voxpupuli/puppet-staging.png?branch=master)](https://travis-ci.org/voxpupuli/puppet-staging) +[![Code Coverage](https://coveralls.io/repos/github/voxpupuli/puppet-staging/badge.svg?branch=master)](https://coveralls.io/github/voxpupuli/puppet-staging) +[![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/staging.svg)](https://forge.puppetlabs.com/puppet/staging) +[![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/staging.svg)](https://forge.puppetlabs.com/puppet/staging) +[![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/staging.svg)](https://forge.puppetlabs.com/puppet/staging) +[![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/staging.svg)](https://forge.puppetlabs.com/puppet/staging) + +Manages staging directory, along with download/extraction of compressed files. + +## Usage + +Specify a different default staging path (must be declared before using resource): + +```puppet +class { 'staging': + path => '/var/staging', + owner => 'puppet', + group => 'puppet', +} +``` + +Staging files from various sources: + +```puppet +staging::file { 'sample': + source => 'puppet://modules/staging/sample', +} + +staging::file { 'apache-tomcat-6.0.35': + source => 'http://apache.cs.utah.edu/tomcat/tomcat-6/v6.0.35/bin/apache-tomcat-6.0.35.tar.gz', +} +``` + +Staging and extracting files: + +```puppet +staging::file { 'sample.tar.gz': + source => 'puppet:///modules/staging/sample.tar.gz' +} + +staging::extract { 'sample.tar.gz': + target => '/tmp/staging', + creates => '/tmp/staging/sample', + require => Staging::File['sample.tar.gz'], +} +``` + +Deploying a file (combining staging and extract): + +```puppet +staging::deploy { 'sample.tar.gz': + source => 'puppet:///modules/staging/sample.tar.gz', + target => '/usr/local', +} +``` + +Staging files currently support the following source: + +* http(s):// +* puppet:// +* ftp:// +* s3:// (requires aws cli to be installed and configured.) +* local (though this doesn't serve any real purpose.) + +## Author + +Primarily authored by Nan Liu + +## Contributors + +* Adrien Thebo +* gizero +* Harald Skoglund +* Hunter Haugen +* Justin Clayton +* Owen Jacobson +* Reid Vandewiele diff --git a/environments/production/modules/staging/Rakefile b/environments/production/modules/staging/Rakefile new file mode 100644 index 0000000..d00f247 --- /dev/null +++ b/environments/production/modules/staging/Rakefile @@ -0,0 +1,33 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet_blacksmith/rake_tasks' +require 'voxpupuli/release/rake_tasks' +require 'puppet-strings/tasks' + +PuppetLint.configuration.log_format = '%{path}:%{line}:%{check}:%{KIND}:%{message}' +PuppetLint.configuration.fail_on_warnings = true +PuppetLint.configuration.send('relative') +PuppetLint.configuration.send('disable_140chars') +PuppetLint.configuration.send('disable_class_inherits_from_params_class') +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_single_quote_string_with_variables') + +exclude_paths = %w( + pkg/**/* + vendor/**/* + .vendor/**/* + spec/**/* +) +PuppetLint.configuration.ignore_paths = exclude_paths +PuppetSyntax.exclude_paths = exclude_paths + +desc 'Run acceptance tests' +RSpec::Core::RakeTask.new(:acceptance) do |t| + t.pattern = 'spec/acceptance' +end + +desc 'Run tests metadata_lint, release_checks' +task test: [ + :metadata_lint, + :release_checks, +] +# vim: syntax=ruby diff --git a/environments/production/modules/staging/checksums.json b/environments/production/modules/staging/checksums.json new file mode 100644 index 0000000..ddabcf1 --- /dev/null +++ b/environments/production/modules/staging/checksums.json @@ -0,0 +1,60 @@ +{ + "CHANGELOG.md": "f7ed8a26d6ebe0f9a90187e16bb690c1", + "CONTRIBUTING.md": "8df0e5be30b6bca932fb5e34c2264522", + "Gemfile": "38e0d1163b42b36487897d394a6aef55", + "LICENSE": "cdbf8d74b765504fbdf8e154bb4458a1", + "README.md": "a691c44ef6d43d9d8b543d2a11f5f005", + "Rakefile": "5abb3039d1d1652ede4f99b0f646d6ab", + "docs/deploy.html": "d9342a62d811e8d331c83a9521d7cb60", + "docs/extract.html": "ddfc7718ed639b24861b5a1d68cf0b0c", + "docs/file.html": "879efe671f96a8eaeb5fb023ced5b7ab", + "docs/init.html": "c16ebedfb85ab41558ad2fdea679177d", + "examples/deploy.pp": "27e2deca3fd5353dc8aa37c9162b2368", + "examples/extract.pp": "297e67b90bf07b61343973d3b46cb3e3", + "examples/file.pp": "5d658d29f0b99800c463dd3995c9ebc6", + "examples/init.pp": "485c058d83ed2662961ac28c0873c25f", + "examples/scope_defaults.pp": "b620b367fc13aad2ff6f09f0c18463ba", + "examples/staging_parse.pp": "bec31082b383ef5bf6f81e4e82ec3b94", + "files/sample": "c668e2074cc870f9ef59b6b33ffda097", + "files/sample.tar.bz2": "d063a08e52797236df1b769dbdbd2c69", + "files/sample.tar.gz": "eba24f899d8b439f5c461f728563ece6", + "lib/facter/staging_http_get.rb": "07b6449105e1f314e06f83e37c8d07d3", + "lib/facter/staging_windir.rb": "8fc9fd906d84f91eed9a90a32f2843b6", + "lib/puppet/parser/functions/scope_defaults.rb": "398b9a8ed521b8a611536ece0a250b98", + "lib/puppet/parser/functions/staging_parse.rb": "b0cd487f742d5966800dd9ee2d54afc4", + "manifests/deploy.pp": "29b3c7a114f002974760863838715679", + "manifests/extract.pp": "c0f72f295f7648f4f547f7ee297d5118", + "manifests/file.pp": "3cf65fc98691eb104b10fa9992a81138", + "manifests/init.pp": "702557945ee95cf5935ce6ebd910acab", + "manifests/params.pp": "171de83cf4e88f09d3f942c9157c1ff6", + "metadata.json": "74cb40b437d77771662541339790bd1a", + "spec/acceptance/nodesets/centos-511-x64.yml": "ca8258bc835dd985a1754689d124cd66", + "spec/acceptance/nodesets/centos-66-x64-pe.yml": "e68e03dc562bf58f7c5bba54a1a34619", + "spec/acceptance/nodesets/centos-66-x64.yml": "7ffa6d9164a88668fcd51a1988c4dc03", + "spec/acceptance/nodesets/centos-72-x64.yml": "194841a65e8835ac9ee6620e60b58f80", + "spec/acceptance/nodesets/debian-78-x64.yml": "56af2760a64c13a0bccd59404435939c", + "spec/acceptance/nodesets/debian-82-x64.yml": "26f2f696e6073549fe0a844f9a46f85b", + "spec/acceptance/nodesets/docker/centos-5.yml": "7be89192f37653cc187d6d5212d9ff3e", + "spec/acceptance/nodesets/docker/centos-6.yml": "4f135653fce91e0a0547f7caa0ba48b8", + "spec/acceptance/nodesets/docker/centos-7.yml": "5e0899d3028494d59cdcb2fec140ebe9", + "spec/acceptance/nodesets/docker/debian-7.yml": "4fefe3d2526ed8961ec21273799a5e8b", + "spec/acceptance/nodesets/docker/debian-8.yml": "33dfd53fba67debe3962413fd17ffeb9", + "spec/acceptance/nodesets/docker/ubuntu-12.04.yml": "cd32551771ad120f5c060c208ef48f4e", + "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "e0db14c00ab3c01ff88313df2e7b4d5d", + "spec/acceptance/nodesets/docker/ubuntu-16.04.yml": "936ee4f65c533165cea0c610a9071f8c", + "spec/acceptance/nodesets/fedora-24-x64.yml": "431cd85b87a65a55af193a360aa52f26", + "spec/acceptance/nodesets/fedora-25-x64.yml": "d0f3867787d6eec15fb1da4d9a7ee1ef", + "spec/acceptance/nodesets/ubuntu-server-1204-x64.yml": "0dd7639bf95bfb18169ebba9a2bac163", + "spec/acceptance/nodesets/ubuntu-server-1404-x64.yml": "7455367b784060b921360b29a56cd74c", + "spec/acceptance/nodesets/ubuntu-server-1604-x64.yml": "37673118cc3bf052755d65fb5dd90226", + "spec/classes/coverage_spec.rb": "166c74e93a4e70e9de79ae69f3c10e1d", + "spec/default_facts.yml": "3da74b0aff340a4fbcca9cc4eba104c1", + "spec/defines/staging_deploy_spec.rb": "80a7deb02237002d09f9d307e5e5768c", + "spec/defines/staging_extract_spec.rb": "9fb03aa76736d778aa3d1cffd54c0a7c", + "spec/defines/staging_file_spec.rb": "aad9c03cc4056290c57191985a69ba75", + "spec/fixtures/hiera.yaml": "3ac4663394e6a377d001eee1b1a82887", + "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c", + "spec/spec_helper.rb": "a177b1e9e840ff9ab649c51178f2c85e", + "spec/unit/puppet/parser/functions/scope_defaults_spec.rb": "e201417c1f5430b21737cbcd945b0840", + "spec/unit/puppet/parser/functions/staging_parse_spec.rb": "0df09ccc71a48119ca99469d1adef648" +} \ No newline at end of file diff --git a/environments/production/modules/staging/docs/deploy.html b/environments/production/modules/staging/docs/deploy.html new file mode 100644 index 0000000..fdcdc34 --- /dev/null +++ b/environments/production/modules/staging/docs/deploy.html @@ -0,0 +1,113 @@ + + + + + deploy.pp + + + +
+
+
+ Jump To … + +
+ + + + + + + + + + + + +

deploy.pp

+
+ +
+

Define: staging::deploy

+ +

The define resource extracts compressed file to a staging location.

+ +

Parameters:

+ +
    +
  • [source]: the source file location, supports local files, puppet://, http://, https://, ftp:// (default: )
  • +
  • [target]: the target extraction directory (default: )
  • +
  • [staging_path]: the staging location for compressed file. defaults to ${staging::path}/${caller_module_name} (default: undef)
  • +
  • [username]: https or ftp username (default: undef)
  • +
  • [certificate]: https certifcate file (default: undef)
  • +
  • [password]: https or ftp user password or https certificate password (default: undef)
  • +
  • [environment]: environment variable for settings such as http_proxy (default: undef)
  • +
  • [timeout]: the time to wait for the file transfer to complete (default: undef)
  • +
  • [user]: extract file as this user (default: undef)
  • +
  • [group]: extract group as this group (default: undef)
  • +
  • [creates]: the file/folder created after extraction. if unspecified defaults to ${target}/${name} (default: undef)
  • +
  • [unless]: alternative way to conditionally extract file (default: undef)
  • +
  • [onlyif]: alternative way to conditionally extract file (default: undef)
  • +
+ + +

Usage:

+ +
staging::deploy { 'sample.tar.gz':
+  source => 'puppet:///modules/staging/sample.tar.gz',
+  target => '/usr/local',
+}
+
+ +
+
define staging::deploy (
+  $source,               
+  $target,               
+  $staging_path = undef, 
+  $username     = undef, 
+  $certificate  = undef, 
+  $password     = undef, 
+  $environment  = undef, 
+  $timeout      = undef, 
+  $user         = undef, 
+  $group        = undef, 
+  $creates      = undef, 
+  $unless       = undef, 
+  $onlyif       = undef  
+) {
+
+  staging::file { $name:
+    source      => $source,
+    target      => $staging_path,
+    username    => $username,
+    certificate => $certificate,
+    password    => $password,
+    environment => $environment,
+    subdir      => $caller_module_name,
+    timeout     => $timeout,
+  }
+
+  staging::extract { $name:
+    target      => $target,
+    source      => $staging_path,
+    user        => $user,
+    group       => $group,
+    environment => $environment,
+    subdir      => $caller_module_name,
+    creates     => $creates,
+    unless      => $unless,
+    onlyif      => $onlyif,
+    require     => Staging::File[$name],
+  }
+
+}
+
+
+ diff --git a/environments/production/modules/staging/docs/extract.html b/environments/production/modules/staging/docs/extract.html new file mode 100644 index 0000000..0f46958 --- /dev/null +++ b/environments/production/modules/staging/docs/extract.html @@ -0,0 +1,176 @@ + + + + + extract.pp + + + +
+
+
+ Jump To … + +
+ + + + + + + + + + + + + + + + +

extract.pp

+
+ +
+

Define: staging::extract

+ +

Define resource to extract files from staging directories to target directories.

+ +

Parameters:

+ +
    +
  • [target]: the target extraction directory (default: )
  • +
  • [source]: the source compression file, supports tar, tar.gz, zip, war (default: undef)
  • +
  • [creates]: the file created after extraction. if unspecified defaults ${staging::path}/${caller_module_name}/${name} ${target}/${name} (default: undef)
  • +
  • [unless]: alternative way to conditionally check whether to extract file. (default: undef)
  • +
  • [onlyif]: alternative way to conditionally check whether to extract file. (default: undef)
  • +
  • [user]: extract file as this user. (default: undef)
  • +
  • [group]: extract file as this group. (default: undef)
  • +
  • [environment]: environment variables. (default: undef)
  • +
  • [subdir]: subdir per module in staging directory. (default: $caller_module_name)
  • +
+ + +

Usage:

+ +
$caller_module_name = 'demo'
+
+class { 'staging':
+  path => '/tmp/staging',
+}
+
+staging::file { 'sample.tar.gz':
+  source => 'puppet:///modules/staging/sample.tar.gz'
+}
+
+staging::extract { 'sample.tar.gz':
+  target  => '/tmp/staging',
+  creates => '/tmp/staging/sample',
+  require => Staging::File['sample.tar.gz'],
+}
+
+
+
define staging::extract (
+  $target,              
+  $source      = undef, 
+  $creates     = undef, 
+  $unless      = undef, 
+  $onlyif      = undef, 
+  $user        = undef, 
+  $group       = undef, 
+  $environment = undef, 
+  $subdir      = $caller_module_name 
+) {
+
+  include staging
+
+  if $source {
+    $source_path = $source
+  } else {
+    $source_path = "${staging::path}/${subdir}/${name}"
+  }
+
+
+ +
+

Use user supplied creates path, set default value if creates, unless or +onlyif is not supplied.

+ +
+
  if $creates {
+    $creates_path = $creates
+  } elsif ! ($unless or $onlyif) {
+    if $name =~ /.tar.gz$/ {
+      $folder       = staging_parse($name, 'basename', '.tar.gz')
+      $creates_path = "${target}/${folder}"
+    } else {
+      $folder       = staging_parse($name, 'basename')
+      $creates_path = "${target}/${folder}"
+    }
+  }
+
+  if scope_defaults('Exec','path') {
+    Exec{
+      cwd         => $target,
+      user        => $user,
+      group       => $group,
+      environment => $environment,
+      creates     => $creates_path,
+      unless      => $unless,
+      onlyif      => $onlyif,
+      logoutput   => on_failure,
+    }
+  } else {
+    Exec{
+      path        => $::path,
+      cwd         => $target,
+      user        => $user,
+      group       => $group,
+      environment => $environment,
+      creates     => $creates_path,
+      unless      => $unless,
+      onlyif      => $onlyif,
+      logoutput   => on_failure,
+    }
+  }
+
+  case $name {
+    /.tar$/: {
+      $command = "tar xf ${source_path}"
+    }
+
+    /(.tgz|.tar.gz)$/: {
+      if $::osfamily == 'Solaris' {
+        $command = "gunzip -dc < ${source_path} | tar xf - "
+      } else {
+        $command = "tar xzf ${source_path}"
+      }
+    }
+
+    /.zip$/: {
+      $command = "unzip ${source_path}"
+    }
+
+    /.war$/: {
+      $command = "jar xf ${source_path}"
+    }
+
+    default: {
+      fail("staging::extract: unsupported file format ${name}.")
+    }
+  }
+
+  exec { "extract ${name}":
+    command => $command,
+  }
+}
+
+
+ diff --git a/environments/production/modules/staging/docs/file.html b/environments/production/modules/staging/docs/file.html new file mode 100644 index 0000000..38aa0da --- /dev/null +++ b/environments/production/modules/staging/docs/file.html @@ -0,0 +1,178 @@ + + + + + file.pp + + + +
+
+
+ Jump To … + +
+ + + + + + + + + + + + +

file.pp

+
+ +
+

Define: staging::file

+ +

Overview:

+ +

Define resource to retrieve files to staging directories. It is +intententionally not replacing files, as these intend to be large binaries +that are versioned.

+ +

Notes:

+ +

If you specify a different staging location, please manage the file + resource as necessary.

+ +

Parameters:

+ +
    +
  • [source]: the source file location, supports local files, puppet://, http://, https://, ftp:// (default: )
  • +
  • [target]: the target staging directory, if unspecified ${staging::path}/${caller_module_name} (default: undef)
  • +
  • [username]: https or ftp username (default: undef)
  • +
  • [certificate]: https certificate file (default: undef)
  • +
  • [password]: https or ftp user password or https certificate password (default: undef)
  • +
  • [environment]: environment variable for settings such as http_proxy, https_proxy, of ftp_proxy (default: undef)
  • +
  • [timeout]: the the time to wait for the file transfer to complete (default: undef)
  • +
  • [subdir]: (default: $caller_module_name)
  • +
+ + +

Usage:

+ +
$caller_module_name = 'demo'
+
+class { 'staging':
+  path => '/tmp/staging',
+}
+
+staging::file { 'sample':
+  source => 'puppet:///modules/staging/sample',
+}
+
+staging::file { 'passwd':
+  source => '/etc/passwd',
+}
+
+staging::file { 'manpage.html':
+  source => 'http://curl.haxx.se/docs/manpage.html',
+}
+
+ +
+
define staging::file (
+  $source,              
+  $target      = undef, 
+  $username    = undef, 
+  $certificate = undef, 
+  $password    = undef, 
+  $environment = undef, 
+  $timeout     = undef, 
+  $subdir      = $caller_module_name
+) {
+
+  include staging
+
+  if $target {
+    $target_file = $target
+    $staging_dir = staging_parse($target, 'parent')
+  } else {
+    $staging_dir = "${staging::path}/${subdir}"
+    $target_file = "${staging_dir}/${name}"
+
+    if ! defined(File[$staging_dir]) {
+      file { $staging_dir:
+        ensure=>directory,
+      }
+    }
+  }
+
+  Exec {
+    path        => '/usr/local/bin:/usr/bin:/bin',
+    environment => $environment,
+    cwd         => $staging_dir,
+    creates     => $target_file,
+    timeout     => $timeout,
+    logoutput   => on_failure,
+  }
+
+  case $source {
+    /^\//: {
+      file { $target_file:
+        source  => $source,
+        replace => false,
+      }
+    }
+
+    /^puppet:\/\//: {
+      file { $target_file:
+        source  => $source,
+        replace => false,
+      }
+    }
+
+    /^http:\/\//: {
+      exec { $target_file:
+        command     => "curl -L -o ${name} ${source}",
+      }
+    }
+
+    /^https:\/\//: {
+      if $username {
+        $command = "curl -L -o ${name} -u ${username}:${password} ${source}"
+      } elsif $certificate {
+        $command = "curl -L -o ${name} -E ${certificate}:${password} ${source}"
+      } else {
+        $command = "curl -L -o ${name} ${source}"
+      }
+
+      exec { $target_file:
+        command     => $command,
+      }
+    }
+
+    /^ftp:\/\//: {
+      if $username {
+        $command = "curl -o ${name} -u ${username}:${password} ${source}"
+      } else {
+        $command = "curl -o ${name} ${source}"
+      }
+
+      exec { $target_file:
+        command     => $command,
+      }
+    }
+
+    default: {
+      fail("stage::file: do not recognize source ${source}.")
+    }
+  }
+
+}
+
+
+ diff --git a/environments/production/modules/staging/docs/init.html b/environments/production/modules/staging/docs/init.html new file mode 100644 index 0000000..da5b2be --- /dev/null +++ b/environments/production/modules/staging/docs/init.html @@ -0,0 +1,87 @@ + + + + + init.pp + + + +
+
+
+ Jump To … + +
+ + + + + + + + + + + + +

init.pp

+
+ +
+

Class: staging

+ +

This module manages staging and extraction of files from various sources.

+ +

Actions:

+ +

Creates the root staging directory. By default files will be created in a subdirectory matching the caller_module_name.

+ +

/opt/staging/

+ +
          |-- puppet
+          |   `-- puppet.enterprise.2.0.tar.gz
+          `-- tomcat
+              `-- tomcat.5.0.tar.gz
+
+ +

Parameters:

+ +
    +
  • [path]: staging directory filepath (default: ‘/opt/staging’)
  • +
  • [owner]: staging directory owner (default: ‘0’)
  • +
  • [group]: staging directory group (default: ‘0’)
  • +
  • [mode]: staging directory permission (default: ‘0755’)
  • +
+ + +

Usage:

+ +
include staging
+
+ +
+
class staging (
+  $path  = '/opt/staging', 
+  $owner = '0',            
+  $group = '0',            
+  $mode  = '0755'          
+) {
+
+  file { $path:
+    ensure => directory,
+    owner  => $owner,
+    group  => $group,
+    mode   => $mode,
+  }
+
+}
+
+
+ diff --git a/environments/production/modules/staging/examples/deploy.pp b/environments/production/modules/staging/examples/deploy.pp new file mode 100644 index 0000000..cf9ac58 --- /dev/null +++ b/environments/production/modules/staging/examples/deploy.pp @@ -0,0 +1,4 @@ +staging::deploy { 'sample.tar.gz': + source => 'puppet:///modules/staging/sample.tar.gz', + target => '/usr/local', +} diff --git a/environments/production/modules/staging/examples/extract.pp b/environments/production/modules/staging/examples/extract.pp new file mode 100644 index 0000000..aca9e81 --- /dev/null +++ b/environments/production/modules/staging/examples/extract.pp @@ -0,0 +1,25 @@ +$caller_module_name = 'demo' + +class { '::staging': + path => '/tmp/staging', +} + +staging::file { 'sample.tar.gz': + source => 'puppet:///modules/staging/sample.tar.gz', +} + +staging::extract { 'sample.tar.gz': + target => '/tmp/staging', + creates => '/tmp/staging/sample', + require => Staging::File['sample.tar.gz'], +} + +staging::file { 'sample.tar.bz2': + source => 'puppet:///modules/staging/sample.tar.bz2', +} + +staging::extract { 'sample.tar.bz2': + target => '/tmp/staging', + creates => '/tmp/staging/sample-tar-bz2', + require => Staging::File['sample.tar.bz2'], +} diff --git a/environments/production/modules/staging/examples/file.pp b/environments/production/modules/staging/examples/file.pp new file mode 100644 index 0000000..ab335fa --- /dev/null +++ b/environments/production/modules/staging/examples/file.pp @@ -0,0 +1,17 @@ +$caller_module_name = 'demo' + +class { '::staging': + path => '/tmp/staging', +} + +staging::file { 'sample': + source => 'puppet:///modules/staging/sample', +} + +staging::file { 'passwd': + source => '/etc/passwd', +} + +staging::file { 'manpage.html': + source => 'http://curl.haxx.se/docs/manpage.html', +} diff --git a/environments/production/modules/staging/examples/init.pp b/environments/production/modules/staging/examples/init.pp new file mode 100644 index 0000000..af50cc7 --- /dev/null +++ b/environments/production/modules/staging/examples/init.pp @@ -0,0 +1 @@ +include ::staging diff --git a/environments/production/modules/staging/examples/scope_defaults.pp b/environments/production/modules/staging/examples/scope_defaults.pp new file mode 100644 index 0000000..3073ff4 --- /dev/null +++ b/environments/production/modules/staging/examples/scope_defaults.pp @@ -0,0 +1,7 @@ +Exec { + path => '/bin', +} + +if scope_defaults('Exec', 'path') { + notice('good') +} diff --git a/environments/production/modules/staging/examples/staging_parse.pp b/environments/production/modules/staging/examples/staging_parse.pp new file mode 100644 index 0000000..117ad16 --- /dev/null +++ b/environments/production/modules/staging/examples/staging_parse.pp @@ -0,0 +1,12 @@ +$file = '/etc/puppetlabs/foo.bar.tar.gz' +$filename = staging_parse($file) +$basename = staging_parse($file, 'basename') +$extname = staging_parse($file, 'extname') +$parent = staging_parse($file, 'parent') +$rbasename = staging_parse($file, 'basename', '.tar.gz') + +notice($filename) +notice($basename) +notice($extname) +notice($parent) +notice($rbasename) diff --git a/environments/production/modules/staging/files/sample b/environments/production/modules/staging/files/sample new file mode 100644 index 0000000..01e34c3 --- /dev/null +++ b/environments/production/modules/staging/files/sample @@ -0,0 +1 @@ +sample file to test module. diff --git a/environments/production/modules/staging/files/sample.tar.bz2 b/environments/production/modules/staging/files/sample.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..f7fbcf229f0fc0c218a89c7de41d9b62ef6406b7 GIT binary patch literal 27984 zcmV($K;yqcT4*^jL0KkKSxQcpxd7Ple}(dp0RVsh|NsC0|Nj5~|N8&}AP@iuV3NLh z_QP%hk8;A99oghyk_A`9`rW>=fj|_X4_C92g1QT^S66m)g(`FwvCS>Z1&?_6NlaPMyd-g-IR8=y?rcN|p!=p5~A-$s;P-romz zj6*9uzM7tZdv_UkPOh~ygR#c3cDfE4RgUIe&34JvX5F-xHEA; z%_-*q$HPqs2m%2B0%@V5V?<=hrlSdyWj2bQ(LE{Yphg;G2q6SUfDlPMDt=V;GgAks zN2!o##AGr6XlTMg5+oBr1k)oafgY4SMu({NG&C9qqyPW_0SQSE5i$m;`VyYd#YRV{ z)714cY>}FSKn8|^pa2MxAP@uu(8;MhfSFA+%_d@sc-1x|O$|>deu{b~6VwuFLIi1~ z1WeFsX{myrq}4r7(^CjXsj_CI007Vce~5p#{~z7|A1`b0$TD}3CW0;o9}9B07H{VI zbHA^yL8V2bK%r><_@6h|&&G>eFZ+L|wEw0*W?<9=XV@4%{v(JDQ8f)w8&Kqm34(`V z&VVAMbdHTW_Lu2@Txb|$;C_#}` zsyj8H&k9)%ZRN02K=<}fIa1OQ1Wh7R%uj2$K|*}eok2?x&4icA7k0*aj&O7h5$j}N zsAIz^g1ci?4vjUfW~Ig}b;L6PD#1ad##PTcGReboT^6X?lZw=_dSoET$qNEIfKP-) z|L3nLC;0bay*+{8mpVW???eD|Tqv9VPq){r!D<9XbBJve`yHswg_1U36Pfy^d!T zUYi}%UGmW4)IyHR;B5Zbk3y37AniP_-R)JyZq8ee01eNl`W>dPkSBusEYo1`loB}q zRT)8V|T5N_^kL&sdW%uMx0X9n5vLtn;?Tgz(fx|xd+s)(D+ihS|@I2 zU9b?CC*j&PFQ{=OJNpM(Ll`|_2X#mC+fCY^5kdTeT7{inc{bIn6=_7KFMS)$R{~j@8yx*?@+unWzSq*GOiL*>wkhh^mMF~K=`WTH}wq371z1kI{ zj*V2&twE1}vhBHM4>=)4O9C~U7yzP{#fY%lcQhuPMlnbcf#xTtn*J-}u+&c~Q%gWa zq0nhEllmnCmuPfpW>2vBP5C742dQ~&pCjOpj$DnIVjSLg|0S7*kro_d1&*;)z0ya1 zvSnu*!Rr2_h_ zNQYxaizYPTMWM<3OgnZOo9Inw;pOr&j~fmoNLBB?BSqb45r~JnWg^KG9Py0Eo2?sp zDkemfGSVWVO_o}HNW5N-ZPh;%Ygo&*Orp^aYbcAY=5O;`)>sQhF{2}I$l9h*EJN(( zWReJqhdJTG6(UdI?pH;-m*Y9sI+e#*#`<1ipS8?t4G@QSal7iS8|ooZVYZ=4KV)6E zyPE-cIAA6;Sja(Wg=6gc?b>Q1S-+qjHSTOmOL0jH#{e)Jt)dSD%Z!5-g!$Ce41w^K zmiG5a#+kwE3xTcfFi{3Wfut?43mJ!y)1`hrfnm%?B6RK%&=7Kh{p?=~UW{pqs>7471V-w$b3-CmoCJgaag_ zDU9k?5!|u$;?ktQh}Xqu9iyl+a99&b7CEzlXz?)f=EgWdduO6v`NbJuMZx+b|4S?0 z);B6myiUv%sE<&Hg|F1meL#q|zN=zTJ_;l=GzukXXj&D$5oQoe%3Mb&6+luJDE&$) z-|Bbo?us?GSzDWg@ELlc>MLHE*qRtJl21rqm&cO)XW%0_kNvZUu|Q2={I5z9M2m>8%pDE5c`{&DzZ}Ic+N+TY%kfG<96VD=On0 z&9o!u1q@rV8HREVlEiMzX}s8KLlRiRv14)=_`7C)yIJzjF9+M_mPut4VeAgJ+uO^$ zZ;_I%gAn2nxxEz^$`ijc`cfoMoN5D38QUq(q+;UP(*|U)&Z$|%D$%)8M$(Wxipm$y?M+Qp1dp>lWb+;wg4Oax<^i93I|A!2XmF=jFcbeKlpTsppRk z;=ulKhr%8eM>VbiQHyZ=->9gmZ6s5RYd~PJvX3%VX?2SCK+K9GF$BA~BAvk@nGd$! zNUMF|PwBDA?ncgtJYNdafWIeJ0BfObNkYl#GyEhQf0Q4U1>kR(-# z6<HV7RcbfvU)cBNE&$G?;1;HltDs9}OYe8=DeG zJK@@x`z|)aBgvzhuIkAx#}NG5L_Sm$eCi%$R0>XE5;H}>E&5u?Yf$B5(cGR?d=_^5;<8G|qelTb4?+5frLqFh+BiF`qX8`G)dsc;TG#$PtWrwc%x zyZjT1zb-+B7Ylqn8hBOnJF%a>;^MpRsvdp>SvVV4mpmXm1$jFsS2+caD_~{boKdg@ zN?~7G&=eex9Q|ZtBb+aRTo4K1?1gaS*m0YH=>cbgGJ6ml&#_W(YbW4F>;%JjbME$2 z^7~MZ=#Y$swCBwi!YjbB8_RZ^DEWp9X~`v@lwC~aai$j9?QVhmAa26Rq?*t2k?#2; zdIRS7*boPzO?%n{pYQcVA74~4j+{j=#;g}fY({f33RBArehbtA?ydX0h4} zKe2~t9PN9$_jt_mM(H=i&@gm9knB4V$qOhC%&G)lp9|Zp2nOPT6p=`<3M;!a>*#Fr zHh#GXh+I?;)@DEA-t}KE*U~s7k7c}zo^{6I{ct|X@11EZud`brA=rRzRcuY|-s|T< z!W@~CmUY<6ZJtHx)e?v8y>r?#PK+(Syft!h)%Z$#m=`F%UY@6=#UmE|2<`jeareA$ zbk19XN`)=J-3(0g$-ILkuZd_NKSM5EF{Y4oAchMv-Wf%OE!xnpijT2qle*knY~w5l zm5a8Kad9hNVC;8R$qK`mhYxfXZdI^mMN8r1aGl_76}EvYkltzj(!0le!=xf2U}mcs zm=9rG7A7t00j>k_Emq?#s5tfyRMfzj@zukfYXM zVA(M2jKdH>p4ALA-I}nqp4Xug1;VP4n@~qQ;3pU&h*W&{^fQt#`Db(Y>yN)}Y5-Pn zy(rnuWyA#dhtDLz8m@V?~utT~N3CT2H*1l4> z?dLtefJ2>x*(mUm<#{Z>Ie+fJdLIcG{-HegoN#8yz~S4_j~RTRNIt&>)$u1ku>1D1?0D46e8J|l-%Bo$4;afUE7GZ%v12FK_n6(7NQN< zc)Ab8rg=}pCTA{1?j6{S9T4jxz*`}ZG#BFBPdr_LzxKP5&VcU2p4YqGuy$|>(U>6z zh{&_)V$bvnu={ovLBqOH4kt9RRDWWYfij+VyK<+cGxODOf^Iy%bM|$HrgBLaZeLFj z=5bHYNLE3K4jw5FMf35ok>b@3bGu!?XWn3q7_-H2S7tbyK!%^?841?IRgLkM(#d5f<&-#dL&HjR|{;&KgU zL{>lIg`@GVD+$6gk2GR+q^OKa4paAU>*Ve2YQQKJ`m}gjS})M-M0c?1)#aFu>V<9y zxU21pY=?W>-kfh(#*h;h;)^Sw8ByTh^mv;$Avt#~^UrxGCJ=Rnvd%>C`n?Rb1cDCq zXvr;;>-Itn@nWlGj04@)(fO7c5$5K=wI#;k#4V^xdPC`rNDzz+u7d%mPw-*1t;#(T z)Mi5Hh=l{)@l717=nnnb1NTgD)pde5&U5{Rxe=J|R{ZiCR=_SF3&O@UBK4n ze|CF%ZwAcw5rFO|;e{USaJH7(mJEnvF`42u)nA|&y$V3);|4?*ly0v~!pcFyli%F; zQ??5o)DQUM(P5#k&<-yCv&g9;=`S^}LPEn+fQjkdq|dw2Ond$@iQdzlY_-W)D_70x zalcJb3fR(}(EFKpb*8LK&9O!op+GY#0sD|LJrbLGMFoG_{rkR0E`~Gz1!+emZ29^1 zCD%9kV)C_8>~h2ulABoU1x|t_Kkn*3!tadYj-S`V>VO^Zj3tiM z7rH>G`)e;2@ox23sP6t{mQ!9AwR5`|pa617Jnc4L^c{0K?3wKX#@GxReAj8t*i@lk zt-Tt_`W^W|ejiWIeqdNLG_-nw&KnZA{GDn)Uju_dwvgQf{Np#e6ObgM^VV$keI*!* zEOB4U<6R#He7EDHAJa0?>gt7L9mIi1M2Q%ekPt*1$0Md2h~9eJstCEO>N#h4 zfrcO{i5zeQ?_qK=5FnAGMU{HlFfXa><6H8rb4{JBd3B&MikrUyg`6f4Y|?l$Z@_sW z=xDQi;y!+?-M(G;DKJAzu!s&ECfP#UJB?%dcj?QbX_uIIKf<;&AHB48kx4jR5^r$V2bx_3_PG%b}=XfzGX% zyNgso9pUH7xN;D!bdxeNDts}<`**MzRp=7zS|ApQYsqc1aA*3T98u5Ftgxk!cQn0Q zik#c{(3~!`p|X9lyyJ)NMV7>CM2H4ICkvd*L;>vXJ89+1@6zB{E!H@OZ2xgJc0we; z8i2kv)dy7yCKOk0YlRE+)JgJd$KN(t`#k6Nwy`#H^=?se4(h4c{Bd zPmBm6QGv{Y6K_!#4xWM(6Zcw54u&%Lm=D!+({(&Iw{F|FAcLY2aTj#9>e2)E1Ukv7 zXB)X_LnQUI`JsUCN)GzX9_JS0IqkY=7I5Hf+@?kM$S(&X(Q+thKB!Zj09@VhD{>A@ zyVR_75X(_e;I0VnZ$j@RRVU%>HSaJT^f=El2vF7#mJ$>3fmgLxm~8RYX=8Ug2V4Nm z2s#UHW%;gfFh%jI{WHXf++gigp1PU3ZDPk2mZ8>TBshg820>3*_*{xG9r1wTUw{;b zsLX4wbMK7MBS>cUBa7t=esmVHsBIcA~dfSX$0a33-=M?+l^4wmW zO0k_kh`?FVd0KKQM+0Q{ht!6_Yf>%Woko}(^G4&gr(9tRA{HP} zUL_ZKdvV@}ZV@Dk?VO?Ny51}u_(40lq%%Z{?jsIhv+olF3_mAwZlqV->|FOZqmIA9 zfxL^uRJH$#-q}!^p2ey4LbHZ|JrH0S@85LK1z@r89Rh@=**=pERFx5$tzQhWYrpU# z$1OC<#_AKJ>^P@^_FR^1U1P5ws?M@JHO~(KY%u_{Fa}FJaq8%<1#jIYC6Mo@WN3Q;L}Eo;ze9qG|DYA3yRhiZw%ru&3H9~fc9oyc^E-V68`jt z+XFfN-{q@}8!;+K>{SO;9;Pul^Diw~uhXx~+%hrr)}eZlKc{N;H%X1y&fsSNMw23P zei2yrL@^s4o7Gs{l^9Z5=Y^I`K-fPp{A_SlRze(hUFd&_#c`eW#}IunSVOyy!a_0^ zAII5q^;3Loo_(XK`+9ccdp61ac#mFnsb?=%`JLNeTu0%4|~(CGT~ z9J`kn-plVQeBHmeNV@`5wygcFLs2Ezc*9|jzWK11VfrRjfw3Z-i=j?&2ai zWB`vIDg!$8gbRq@WB`)UfHmC~y>x~!5#SK}(}^HxBpShGrJ@{{aOpTR>VSg!PWJmI zjE9J-2qR%rw+#{1(TwSvi9{Aip>~i`XQf{1WW-lrI%m9P0Em!D=9{)`2bfghZQ!lFAphYAoQ6tLjF(aPx$n@RK zu|1!L_0p#8!}9TD&s_lsAHqT#(tm&R;GmR(i<+3cHwY7k*qcL{6==BupT_&}5i&K_ z?^xRn9D@-;PQxVlLxXc^_4hzHlujlDQi5j7umDXV?=0jRKd#)11uvh|`rdlG!w-`3 zUX7r){9m*7RoujUsfOitr`9KL=^`YAcVf(XVYcLOjU;+MCt;m|p!1x&nEWTyKh5AL z97aowHGa7K=Yr;qUu5lB=W&cIU_NqagIGT&VOqsjlOadhI9VFHq`Kc82ZM~-!$YKq zk)A=fUkNqx{@J7Ipdrm!8X?Mo0--~Xz~{HueTCL-<<-&-L$}Xq&eN;2MW< z!EohOCP5b3sIn%IkV6>=LFS$nbC|J_*1fWBh9}6?jTx;$u+rN_qLa8zJ$Xj6gv6ir zLT>d_p_{$@tiSM|hsou}GWS{e#) z?HIu2pK+e^5Xd3|em{?Q=&#gG^O-L#C5ouW2F`{W(RdNP zbTG?V_5(-Em}9KQS;-9BQG0&z`rXqZeq5|eMu)^_%RSmbOyEy+Ir_mzR6qH-@V1hmJ2%Zt6D@1B^Yn` z`7yTpY^QCF?!8zZ-rVZrDP1=X+MB4w?ax0&|HpsH{XY*cKbPU=^M3w*zvbKA+x|X2 zKb!RV{yu)cA76W?&DY!C|BqLL$KB=g`n;Xp-mg!m)#?AAPovTFdjE&l5$lwFcXR#= zyyF3=Q+-ESqPwg3UboO$yc>8R3@_9aQJiJli zDoG#`h#>_=B8or|sse^80zaGH75r5JE7U*nzcKeX z0RSKK*cD4nNA{r5M)f|%8%gZZ&AqFTYgMoMtoF$B%ZT`#-ovKRF?y*Aek3UoLX*NX zb%{NwK+F(;1t^DTP*9L<-TQvj_Fv2L9Sgtt!|?M+e_>}2 zi(U7Bb<`U_!+VQ|Xh6#-?GOp0l5m^=PpF|n0Ch%{Cc#5c?%DvlAaOulG z-rRws0w9Q}EMW)!KTBvyVg7iKDM=ypajrMlj=BU+`To7{C%E~DIRy%YuevPWl8js+PwXu8Ej3UGK{KZU|f)Id70X~0s2Ke6DzWBq9 zN7y~?!;jI1g!#kn;OgA}O+*#Pk;XrY$T7aWDZ$GfTf@a6I=|r3f51-PCH)p^f^(X1 z3;0u^G@id$;servAPkL%t55gn;^vk)FPF2<2g`%Xg*cFWhp>Ex@E*@!+&a@oH^DuF zlhg3s=tTE+CxCuFf$#c$Bp~+>&!?b#z6d^FA8%myk8h{D)A9NHzfZh=p#F*V0s4MF zeLXvVkT3J+)6-?;=T~yMdd7bq$I(_YODa98(96G?|Ifad@$Ro@FmVs zoT8sRhn7nccSb-NyB^okV7a2Fk9ro+_vRgySFpojTm2GYFSollh2%$DhiXDT+oXw* zhqMvBuCVf|B`3c8XaGfPJ`Ab7`Tsxb$HzB}PY=gGFiM<5v%YSue6<_6GTvbc0rCzMy^Z5>b{~UO_a$#TQgYQBJ zyzW8>;g|KwepB;@?(zQync*+c`|q;S)Ivoht}%`SrG*08Hai@Ok|`wzDsUkphR+L} z&c=A#VAabuEbum}+jC&x7S_pHc8(iURda4w;@WaYWU7Uzk`~%ggBfg1jsu9G;M0*);wYs4qk`+qoql1n~Re=ccJ(IpPKKfciDF5O}_E7jfMNXy$@0;F?m^ z_WnaN4b^TEV;vQ#bV%P~-B~ayu?@GvOGO7Q4jdCB!U%SR zHxMcW-MkA6o_UrI4j^_emkl4eqCON#uHU<#K4wP;(?2Szs#-h^_&Px0(4bF@SF?8O zA4rz9jx*f!qSaW0IzNfuYM&0EAH*|v73N{lgBnG@h^OK7`;RcV?fPr)(elWXbDue2 zmx}d0Gc2+)4*qY2NT>D&!G%FogxE<+g1`CkHba$iINO6$f>@(0vM+OFI1ng3)KE5J z_Q4$U%(Yb}q%ktc@(YJJC8h~6fE+c~Qy(q4OEuB?%ddLM4}3CtPDfL7HRW$cUy-$VebRS0r%^-g5|vDV|!|*_mC=O z{8(WS%p5_On}uu*ZagF@T-E!Ck;U3lQ|EP|(#RLj5NfXQ9lNT)!dg~N?3c`?j^gsA z{!+5de?+eZ$h#{zf?)lO8wSjzIZYi#Oq1f?kowFv2<++K`9r>;HhI$LNK!09{db23 zp~8sr>xYifjrQe5&bHZPuUvymKXvz&Ca`O9(f=73B=5%xA~)On_VZ*qUrYY7=L^A6 z7JtW%vb;;(M_{mE+nb1*{Tb^C-UX=&NnV> z!@6T0SNkF;E|f3~GgY=x1Quqs!}AsC->bP&lRVn10gCtH-!l+sWo>A|a;p{Xhu0Vt z2JnslL(%b9P+QQ`c#797;bseNYh2U^wMs9a3$!dqjxtWN&{ct|*#V{tzgf&I;u{5l z%6UB9R_NKX4a44c^I1WS8;=HPV79n}1|gt;^VZ1QiuvntvdqGo?49o7();b|x-|w; z0S9Q^5P}^75ku+rO&{q4#U3E}GeH25%^gWRV5GSJ&3?CsCBIkh<2bBgNE!Y~tC$t@ z(4(yE{Ke(PRQL2b9{@MwGoOzd?|w&#$rBPCezQ|l^7u*W`gzU9`S8oT%fsCWht1{e z^zWj|{EqrY$$`uNpTPouVh8I_sgNK7av2XFA0`*q1;7vXf|`z(DBN`WWrOT~d})4K zUM0juan??EH<=h1nyLoa7J;11;oYc)m^Gv`1;D`6plWd-Q6G$^*#N(Xu>RP3DhBMP zLU1>Uy%84(eA0i&y~?ViTVsrxKX*hqUYp)(|0SUXJNE7RcVY?wk)j9N9PpR=(r| zSK2;bexK~B6%bGHpaI(fVS0Q0z3Vr?KqzwvGv!0Hsn+{wx|2?C-S|2cUVROBAAgS?0Pn|h z2+YxN5oFnFGL|q}ZdZ}w zB$7@UYk;oO`otyH9mc7W(f>@$r&|A}RJ~Q;3pYOvuU$9Kb|kC6325Cdnk$F--FOwE zSGMmK<{ia2a+bVuw>v3d1B%U-;!4@8ozndEsVg?cCeWAy5@4j7u2-h9zgp(C_KS=4jMaSd<{MVF3Y<4*%Lr6&&lex`P$3%nVl$qCX78a+;; zgcT}1WLY2avyX9JJ{}HEk2?5nom_l>P39N8m%a&r0CGBb_f;d-7mebY(%S>-deAqF zC)YW6*Ieat7Q;S`%Hzfs)@ZiRtqVfs56Z@@vS>Wqi`S>RJ?%Zud)oROIXO8dPo)f; zoSzpb(CW!{?v6hS(ZRA!n>arcqbEYt9Gsn*Hgragzna0p!NI}V$;r{l$;oRXzJHMY zmsujZ(30G9GW8-)1+eeQf|CaoMY1t9hsjwmqC$w9&*$e)j#DVccOs6xFhfQ_qhNr-fGxMJdH z)F3-8Hwznth_0O?rT{{iz*&IJyC|(Zjl@8_6)`dt)elHpfBAeZ%g2ZAmoE?Uj{BaX z0u(=d`O70v8t(LcUXR?^Q?J6k-*s#!Ux(GCW?zTAvFCN!&pGXFl;wnc+>w3>st37X zMPQ?iR>F(j=!?%ftu@#0}*1N*x6d;Pz2`RJx6`uHMk z2o8lmh?8>A58myq&)3bvbFQ9;waV?GL29C;Aex8(y8WMCKspxa!-YW|4lykwUON>^ z$UYDlgR2N2Qa~sZW9$I~`*+IYaYQH=l(5^~J?$f;i4ui0w0L8W1M7g$zhNiBfnI)& zCODucmn5L`W+H-$0M*J71bW6ENH}Bx2(eZqER+Ef93O`F*xy+lAn@jX))nRQv`P&( z3&Q}Q{QSKK%+jdZx^U}3{++$k(k5~MU0Q?@3mfU1Z|sGumMr72}8mXfJ3Qldji zq^dF^gqomeps|@jC{RK{?P~To>~Iex9r-ZB$0e-p0KPX z2&fAsi*fun73`s`2c72T4`~lSx3^Amx^&~j{=ND$>^_ZukAIDg!|P<-of-sOkwfxi z7y@x2NiHqk+oxAX)g}MvkG)(<*q|liSNe3wm?1TQ@By?AG0YfU`^G7N@TF=3ZqzOu zTXt#?019P_?QRFY?Ht4E=S3!@wfJ~qSib%o3V{y_PZ-!Jz+ojlY6rYdLZJro(~DXt zi|ouj*Q^NCsz50O=sK4iu}2!%DAA$dcw_R9U9cJ~@!`zVp-qjGuo^| ze;9ByK3>UKMZSrn=k<|cq~lR;;qoBC(r45>@CJ<6kwHmrN14;S&+o@SGaS#i**e@e zn}~}AKv+SGAVw-83lu?C9_ng^?`kOy^7K^pq37gzn?2{auN3LF#F8fCx7KoZTm9wB z#t9>8Br!8O$?>pzqYz<>V3Et5!YST`DFz<;avhD4L(}Su4@rtYVOYym;Wt{WqKFb& zvx+(ZA*2v#fHC2>&qd5HDCATd0%KV?HjxyGql&R&-O$dDMBq3r)vpR%JzhSl>pUlK_;coGG@W%hlgppXT-F%qrGm(8G=Y;?*IcFWaH+^Ms0QO>yHfJE0 z(u!7lk=wwaaWl$5@Gek9UDu~RJ(fIe8+hlh;j?l`92D9};7b83p$(Fybbp?~y@WxG z{oXU)mb*-$6ym7XPXpu7>@>?Grw0>KixS=5<06P|10&Bqhn0}1AjpPJ#0j}M^Ok}r ztYGpv3dgEciWzoMJpDcNi6e0s#=)^22Z=yn!Bn;t!-`L(v3dfVwxLybXJ3hgQw8GCB2%s*4|8wY9KXwzaKN6pDxZW}ugz3=q_j$aKs>7L5M8ht7Mif6{$_TQ%z-@w|t#rcIGkEF&Sg@!eNV zbDZ7`WJG{@Xr+NVtZiaM2q-9;klh7QFg1B-Y>q9$J$luj;q)&)IP-V=$LW#;fg>a# z5|E;%C`q6ohA5z>B%q{XNNF$47^=mB9g{#?HpLRfM9C!tRa*zldV79fM{o%3eU9Bb z`6eMEM3G4bMT|m1NkLQ;P}4+FQ3O!cBTzzLM!_i1QBy@xF(joW7{Q`RKcD?r?$ zlScT4oqRp#=fFRF1I7k%8(a4M#lb)695Lot5A)bNow{d{+t$ijsH&uwl?G_CQ-;^6;)76Akb&-zo+rzWdL+o zN8Bw5!UgO1d%gb!Plx62t-W8$z&$tLDSh1Bsoy-w(apT`3G4MQzv)#?gnc4qjx7HS zSuuLE z1tbS!_Us0O|B@djMF+@<{YIwlI)(x0A}jaOf3N*cI{1t=G5n|Jq-O8bZLnA~rH>rt zIC{T8j(m%B@5#-ljFoF89`KF{9YOn!zAr1*Awzt6bPlss8Nl%j}Q%>`8S64ig# z?1yzw+ZyQ~5V3wiqz~~*Qz9HESt$=@1wZw7`o&GE9dM#0sZ*_i~R`=dEiDcUbCFjSY(W0Nm}@r%s+Usk;@|^jn;KY zGN@;R^#{}YJiDRkEiaUj5Ay9C$y#u1YlnU3{y%PG;yvvI)g1uyG!vSLaAD1;0m?<9 zjTw>E3nAeZT38gpm{2-7xE=+|iWv%xI}04IN;LCkHyN{7?= z^WFK4bb;%L2!j?Ni7J4kq$-4{kQF-&cJW8v{vG**fdU3bFN>FI3PgJ?-dTE~2nm29 z2U~>$F&1_%_i%*jNeDWr(ay-Co&CPP3ic=PjSNatN=Zb8SDUpDGMS`8W+EgSIBLSh zQ3RI6%yhfgiCUfcy0Bm>0A$m$1) zA-`RNmqG8o(Ny8KVFA*l!&cw)5IC*@#8Y0Kc%R*l#m-vKzj$ym^XOxs8*!lqC<`C0 z3I&Om!>tVJv^c8?5KVyfpRN$a9$}5AXfayGxNtBL5ggkFT-c2f5Ez$F1qMQU_+ES-U~skg2w1+?#=et4&T_bE*Cf@hL5!3ZBw`|LZN!@xR96e(9sW%!JdSXu!waj=337N@Krw6PQ>!&^Ws{JZzOJO1XtnreqbfZm4tR-2k zo@cWcn8C0(Fdqik+f(QE??YJ)-SxKkt92f4*U;r80PqORZG9(V3U?W4BbQrJBT%&z zENxb^W`eAU+SqY|hza^3Q8WbRoHZ?RVj>8Pa3rW_QZrHzuwlo=OQ>7v1R`YoDDH|U zT4@tlrxFRublV}Gfl+EkQY5{wpmfCsA&L5a2LM&28WQq4{~vrb`Y^V!Mus1U)}XB! z(@~1cW_P;bz6IC;a!A@f2YK3F>7WsOKP?Jv0I?F`)a}W|w{(iK81OMPUyKbzjUiN> ztt1Q-o;wCLlSxUTq{vj!NRO_Z9FT_eS(q}Y#@NQ=gB!{~c9sdY9Xw@V+8YUF%rhGqRWjm(X|@Oo$kO9P z8XH+4+TKdBki070n!%?2yFpQ9X8C2&$M;NLqiYR?*{0gB8qkaJ-9@FvmtB(8m9-FyPX3`AZ;|sORZ_o z>dP!vl{JSdizU(+TCZ|r<3z&BdHvOuv1rkRA`NLBF$WpdLL9JcHOV;?kqH(fsl{Yi z7Z9O>%5L2OmU#~oncrxJS?T#iU*AL3T)^_Qq1^H034l6*eu0J;MutdmRLXui_bN$& zo`*iauzpCg(uDwC*%#&~)1WczG*vlZWc;!_8=;Hxa|MVs(l}|_P)D##Aj$3FvbjL# zHdXp5RN68zVex0n29(IcgSZa@)L2u&A#n@BsO^Cp86oIS($pPWFsuSUA+4}IJ^*tG z>>J`Pq4SJcoKdF5D%*ODB!G6H^7HJOzGvC`v$5ReK9|$U`Sj)*GPXC?z})+K zz=;~S8Y6*}X%BV@f+T}v>h8|F2Rm|LilFVWP^VV#Ld_!fS4P4Q+}EeN6G`doZeKbN z*D0~Wy6a?l*HM>(NtY}Z%)>?yEZAm=vZdH9(d2DOnvB4b&J%R<0B;^sAgW=|k-zlW50hV$rVNMfX^O?Sb2exewspqyaJ%V`@1on<69&)fCW-P7=n{ zz+|%yxFeuJNKqoNz&=3R?3SnK`_}q9!3@u4*xmYo=TSr-}XpuT_EaQo}*G0J- zI}e*v+qAtHcjp{~$QU@iVabNv#0x7phVZ89{Nr#dF~Lg*iqtyo-h9?WXR`nrNXZ%n zksDY}#PmWwJOlix7&?M*12v z(sb9VH{VQT>zVL(Kz+q`fE}*gA-5715L_UL;NLdlOimw%?AhdUcpyHL)$h@x^tsaL*U197%&nQM|#zz4*;W#6L;D)*Z#~ySZ3>(-*;Sv}U zBpHD!`5sFUaC_xayuD1Zh~e^ZMY3mcQn`DU(ar=SXFbmM#{#mCXmJIAo0^McGOUjM z@bzei=?>c+U^7@?co>Z16UB2m0$7AWNO5XR9iE~GLX^ID%QO;cB4PVhr!4f&<+rMA zXF9-rbOd5aU0}L~Y2qyt>Sa=T-0|0A;NCUgVUK-ji&{$vUjI92pp0Y4g_R!Bj4a>Ms~P&_caE;uwN* zL#Xc!V3;VDpHr@a8#|%a6>OeFMk6j_$e88Xoy8x<?Th z;!a?sc)V{OrKllbIF~OGKEeon)|(_aI?)z>TA17* znJRHU?v}fwV=3qD!JH7v2aun%uU&sx@K{ged7i-dW)HT(*Lq_G0FE8NRRru2>tov3 zqY@7@N|Ya_)h8|2Q@NKBMd`bnw;u@MJ)otM8XV#%NK{rBD!Yt`Bj+`jdnw>C^PWG&$f!KpA5P@7hBZKbab4KA1Xog9w5$ zpF~DeAqaVK20aY%eE8njFxzLI3~jBXO47!TvG2WsFbIdvgqc2*6F)(L1t6?R+un$l zC@AbmZ5-}eSeRgJ5GJPW{Q>=2CYXwYhzGI?Qu2f72c!=pvllExH>1eloR;!s>vZ10 z_=G!M8~<{}j9C{l$a@AMr6+1~4e5`LavsJqsFWfI$}ILqN1zv4@#%B9lt#Huv*?PQ z62)(8g2*9E>wa9r&1tu{i5s;R#z z(`lxCr-R%SdJJPjJs&g3@~kA{hp2cH=`KUVkVEpp;TX*7IpBwSFAtf)g03f5$(|^n zje}pe)oFd;B*q>eui#FC#jL5hDhO4kTpE9#v8{y>3Uar=4DU!<*R@8uH_JmohcI zM2dD{RXGw^Q5zs9u_oe2!P|<#gn%;>9G0pWcT8Ns?ocm~tY>#z+Rm;l}3sU!8<8gJm{fng*Eo z@}xT}dTXYkpiz-UFRXU7W8Y;oAer0Zrt~CE{0YU}yTB)iFS2FdwwYvZmzTpGIyM}< znZCT>@$v5oTqds0Zn!gcow8?c!5kmpl|8J;ez@4mI(LwWeDKU7uo)68V~Ak5$0u9O z4-Y3!+zZ^llX92wnmW+Nu&cZV{k!_fMy2@}gsJ`JI08)oaQ9e_b5+P9=)n;IXRT3T zG9e=@Eym6tSp}MtVNY9CLhH6@4i_!Req!zU7~-NcA)ghYdaeYH2H<;6Z&Zfx5zdq8 zJ`G}>_W{T;i%>|xLTOH+!_f<6|k7ZmQywd@`nkQ{iJnmQK{ zywg}sYn94R9E}#BC)ose0fbVJ`eWc7))^tB@$x?ZhrE5HAEL39pfM(j)LMH{FXU@5 zh*5(RAjHWe8M5}5s^z-jW1P*4EjlzBqAh6(D3%&Ci#1lO6O|pxoRg;$3Wcx$M@iR#xnK48tV4x&IsEm|AZnd%R zf&BzA9H3R+D$0a0{`?CHAYQ3M1rr0hkpn>E3P7?Y2P?FIePj^*aR!J0S-f=Sn4yGV zH;5n8`?9-=Oz~vTv-qWl{c83sO`^FF3xwbxfdC;PUG+lI8z*mgati?E9sh>bL4RBo zaU^6Wiszgx%<9X=i4%MSI2m;;QoRxxs{>>RQWlUq&qpz3N(uW6gRnpj-UxA6L{&NR z%Tr=~Dt8*L!tZtufCGYrKM6zQ3jQ(|sKEl@q$*R&ef4_F)UPB%u>u7^NdeuJ9rmDo z(Jvoe5vM{IhMVqcxgS_NJSL06d?S?4R#^!k>5*+$)EWC&4G#@9NAI;_Xl?is&(Yd5 zRw)jY9sP!wlUc7b3c#R}i;U#9FfP&-vw(@{ANUMFG6oq|2X_1n)xq_l$6sz1_7Z*| z_uv=A0Pe}ih<|zfL5lzT^t=bLzaca~s1+~^?(hz2@5o3*{SlB>Kx9!86-8ImoNP6T zqJpU)f|y{Yj$;*aQ%Dt?5K%V9u0{!b|Ig9}?a|pI`0vey*jvH95-ALt1is1p3>B;X z+ZR6gQ04rUk_H@Q0WsqKT9l;&%W6YyLx^;X2~b*vM5sas#Mam&k{f$fGTdoFR3Ygi zX%dElg`j9?Xen5brD~9>f*J;aDN>qgq$#ReK&FwQqJ$|5LXaW`l9Hln5`bD-XekOJ z8b&1|Xrh2=DGH$qR*FJ^pdx4}pokKgkf2%=hzg;igrt^WsA*V$hzfUyxF_+LgJ}EY z8vM`6`>&s4L89U7&jNW&7uz}P zlv4+wdEo9sct=lhUY!|uoC&5O>SiiR7$Pa4h$f-}B&muBh>4k&WU3k_kXc%Sm>~#~ zf+i^#PBDW8#xbgr2wQYNZS)%u`x!CRD2HNIexif2;wD`Y{ zc_YM-C{6Dl)?h!!`o0Ii3BsJ1D4>levJ8MS5{2R!{y8G9Bz}^*dt-4k)z6Fv$=18N zIDUSIxCc`G`xfcVKaT;WGrxyP==V0)KVO6DnDZnsmHY1#%}O7zVRF%viiL6E3CA_ayn*SoU| zV^+f|t^{(8>OwK^lvrS5jlP6sd-n!X5m0`1#aT?$nS*D->S zOArYilsBJ?f=HNfKFaYJ!aWIOs&IM*fRDi5`fg3&=2K80&epHQmZ5&XhMjOgc+}=_Kq(K=HDHUs58G2V@9so9RUUij07^}`?E0S z%yTIIqzFEv1I#ZxAYkPLqooAqoR_me+{EI*HSp56!d3rfRX%(w*Kc0{RHCITBB~^& zl4^DM<|gTcFA%>czwY^bZ_G8{IY7(59vTf}+P8*5`X8(2Tkjw6i2@A6@U7;=FSk1z zZEMPC(#WBl0o2X~-yQM0sKA?m9&4RtY}&;>26t6>V4AYGKxRU}d~uPFngMlvK6TE6 z?=B~w6aah~4$kyb$A?|ue7Ttr%t=N~xL3vU^!#Sopnx#^z;X{tbc_wVzmFB5MFJiF4X{6yM{7iU!?g1&NZbbbXA4@sVhB4xdGU>oNxf z?TUhwB>@pi#8S{xMIcZsKs7K0DM}P5L>&kIOm}TKYSx$VG?pzhL1S`gV9&uM_KhV9oQ5OPHXOgj?U%lq@mI7%2d>mDE9F_xN?#8Gcb$o5h9WUT7J)sSVARYck9bdnZV(=dVp!5~n|Gv- zX@`<OjTQD|th>S|S7(y6lDSVJ^H3p?A@5nLYBXYR!yFetOJs@83h$YMMfx=(&ss_EOBY4W%+9eVfX_+9wJRo;VloG=_SU1 z_nLX*4;x8!S%YyEUUr(EUq)Rd*G+T^IaS-G%QQfbVGPsYXRYXtTSm{G5G9pHMyJOr z#A0w7ipUL(j<|#)VJvDVSzc2xnIw8k1XjthYB6iB4-S2F;te$J*;FSrX{Hths%oPF zw#Ty;L7_q^$+?AM90@o{Cm3MNgwZuv!HuRjb<(w@QrRrWqg5<{ZyCU@7!8jWq>%wF zX)G@ybcN(7orGxE;DuWnw$r2v+{*3l`*241Zf$eRG`G4tLgT=5!UeRvZfk7BN$xo+-YE<5k>n z#xNd$bmJu|@NXr>U-`5OSVEW8XTpbvq2O`#p)-@Vq1U8oKC}RrD%Qrd9 za-sYn)i+QOk-3aajU+)q_@NxfMWN7OaCY(G&_pHNPD5lsalmH*Pj4R+;vM>HIy*am z2uW>ajjF1Y=ZWL|W)`+%iIbx#UW3%|B3Lyh36fbxF-2Akc&Zez(ep{zac%Je0q+R= zeWDCp*MANXEmai}R6lIu zI-Lwl%6@I*Jg@Hhm{Ydi7DKvqiMWZ|17j#fJBV)}{vL;>5l&1IgxC+s8Y)TK`W!%` zn`)$kX2_dcB5ZFNgDa#WJ9Dm*s5JBtRro9EXa7zHtWdwZqckUeO5S`iL-!t3um&7QH5 za0odzB&AzBN}3*F1k;>kB-8_r8&$#KT$UmYwwy`E$L|8A0|Fx9WPDQ=2uLIg${asn z0M)>e@ry_V4hj(sIRMf6}Z_ekyw7i$m z`E+nFvwIi)&W+JAW*uGz*tUd1(R3f%~~u7F|y~StR#kkhBF_Av&=qka%r6b$UR*R z#pWqqaU9}-n0H7*vZdi*n<_OG(5E!_JMC8AVKpJ51a%_Ya9s;=F-DXPO+^ese30Yp z@jY|cFxlJ?b%d&##>#Vs15-qXsfcPSSe787d4bbOtwl`%0TdM#6zf+ps;Gn%B|iTF zP6uU&HKD5`J2&n0nT6;j+d)e;YFM)aE!uZBqa&j6K7sm|_)X<4y-cH3<}k67PQha*5pHsGNO|iOWVrWD}%byx{?*2*rqp zG<7LCK1W1V=A2Lx-y~2_6F5v4vjRK;1@t*}t`*PcL%RLG&xHYY2dYr-h@Eoy+dm## zO?OgBAxHDK8CSeYp|MUyF;e^*TfcaZpNBz;NDr%M*g}>u4yKJ>N8J^q z4%jr{e}!5`!v{0Q3KiE-ZeS}zq)wOvNIiG!hc?hJQa!k1R@x4vHAzh(V-y-AP0A1` z%W!neL_pB8kEXlB7!6(reI>XiL2QF?fgA+@ut9=iq^hE!YH6vWf@FY*s4)c;5_bnP z;G_xD(qvYYTG?WySO1xBUDlBd%SlYrDAlNKyxi~iqueTic5?cZZd>6rWPYn&4R%VMs-PIz1b~&%ZGf4GlsOL|c`1QeNft$f_n5$e zV6hp82x4F$b->|Igt{~oIT7IQ#@y@@Z-BIg1@B_ij|(Y`f3kqUEFmKl5Hg2& z=5O4Fs1^eiT7uY}fPH@*(2(-`e!JY12@AU<`H)0k)VX5P224;ele8Ryl1Kw}{w{Lp zmg#V_Jcp534~&Up5udfhbYx9{(@WQi8Ds`3m~$~)+CpZPt64LXFv1sPpGskqkcU&> znCe<)hF8h}rFCuje6U-U5==0HKL$)YZ^|5)Tw*UoHoDQl>cz3Yj3FJ0 zluc{$e_9ZJMgqn|;tz9s!kR>U(O=c*0A28!UpMDZ-GZ0hI~j`~us)L&LR2EEBE}31 z3@CyIyu#DqYu>_5_YWwFhlKh5HAFLksGMDaj0~ilk=+>rleAv4 zlzUjmGH4eQF^&f13R=)YgR>6g{)^m0g#BtVKoo*13FY;Ibr6*wRi!CFpeQ003KfU~ zhK*?XWO5}!P*NxX;4>uh_ncL*f{|7Sa~Pt|bpjd*Bi4a=2yHJ!v=tICFp?VTxR``i z`!peI&NR{lgCC-m3Y#0N{l(~?mKOglwk(?hbcVe>X`?3ip!z{&&@3R`DxM;qCqQC4 zfOxV61uYDYr?;^3koP0H!vhsEiy$->(e~DN)IYZ9a6Xd^EGg>+qTe-fGUtj&q6CPC z?n$UNKTHqsczgZ!@AG#~Rf!K-$w%F?O%#w&Qc&KLyv>f`yKuGw7!Ri^ab$x{4ssN3 zhDvijdShd8)-@FKat;(zNKh9nUKqvO7HtnKU=vH)78+%N)Sja7n+Lo%WLOi&kR)v_ z(<5m6|8i0)Fnf~il}iX^R|t0_wk5*a;p`2R{&=!vA#^^-6d-CkbNP;b;d^2Qph?ys z+ED={hIBVTqd0aTup+?H6Tvjo=|jZSz=k4#_1=fIboKwBpQT>)JTfTZPFp74S~X825n^PXO{4yog)C52hxTaC#fFw_rdu zBKVyBJ{xrJD+aZi@u!Bcw*w1vXcK-5pTKo+aR_3<0O_xlQ7+KMRg6WWD_9rO zIjQ@KA+Q*0EbgdL#z`IJ0KvipWk8I`D^`>T$nWROJcvv*CE zSg4a4Ga{)SE@epKL$Hn{67d`9dc9rkh+&N=2i;m=CU|9*dxM`$Tf<<{KKY+w_N0%< zqxHjoRWcDpIF}m$RiR^~LkJ{{iGREyMT20$=}cO#tVDk(@Ct`+m$1 zKK>>RcYf2s-W_4IWB`m8k(1j%iSrWBR6-R{)l^aa-R5TO=q9kP8ke zI3ng3B$7Gm22XtZvcbzrROufRY{b`?$jJnJP01Tc`QR-4`eEl%h5*`esp5wQUA@aa@*psQ^k^%dvoTtQ- z)&S3uG<|8krQ$D)g3TpeXaFAQ z4a=~@2qO7$HymBWfOen?DWRc!{do0r47EKXR6fjjS_a7^7{ECRGtS^>SR0k;qb6Po zsv|qvR;qgwl%koFk)R?bWQd4@B9gzmw!w%fc?D`Nwn|AZ#nc+b!)H?v#2hkpa5=C+ z)3FVstCOs9cUFg;aCub{X&hW98)~jtgJi)AMI{3y6!jW*>xSKP94OHoy12zGRSvRO zR_g5(8ZGAC3t>O@b5#E?XIWZ)itX5sU(ser8?_iV8tw#C9gLw#7L4i^7*Ly_L~4QMDI`;r8hkSoa0CZg+KzFmR5+Au z_oX2Y18@sl)@5l zkb(xfb=uNj(w^d6rbnVU=B&ZoC zq9!5X+^%}{i0H|q5z$&Q@(wTs38K3gcnbo_QlU|zB?UY82Z5pSx?%_hQ8KN{hi!vV z#8HyC)3FwnTG>XYTUBtyVb~05po2w@qgL}DVBdlvvi0w5wTMA|3ZkGNh!`d2qa~Vh& zpF-6^3);Cuw1hOJUKZLYjAZXn!AgQ8EVxdBezO84^HSRVG5~(%HCC}30+Hv3U{nq# z`Mopz2%_x-0@x^aS#g=WC7^8rd&ChmI!WC%*DUpcIq>mLPPyfwrVz$It!|)PA$^^) zJV?|z2DpKabyP&vfrrPzHwCanhQV{#h#0|65=~@L96t{?o*Z%~O|N{zY{a0ONt;BL zU@}1KjEsItBy&-@karUP`?o;gP-r5cAOiq4k3D88yQiP)%yd|Xp$?5Wl*D?;x zy?bQjH+JzTH}upnhcV^}%yZYBu2oZ2MIo{HDL%L7NXn`fCnX1v(m2O zhhZXi28Wu)TmS+@_tSj&^(Z{L<)*PC{VS&}z7|d74#f zOL%AG;EWz^tS}Az?NuIx`jMQ-BjH*cX8@9M^jniJs$9AoG<8Z~AiR0Vfz%jMs@H%VNZ(?Ss-@421d$h~7d$}V z5mtOV|6egfg;qt!Adus=_0)HoaD4G%6ACDt?TvB(A=&Kn=lZk@3VQ#i{(t`eYkqu{ z?E^I4K+|gF&NayN2V5V(v6_i)M8u1#6}Qc#MLZx>K#*p9Y%22s zcWe_-$XNboEDVdypdq$&h8{7+9@%|_qJw*xN<0IyDIn=Wg1{+6YODdv?&=sp1|qRX z3@Ruf&9oF(DM`I1(-xM7~JMio5?EwuxiY{LbOrs!>i9sZ5QL66N?*fwfN*YvfK(VHt#-3RkTajQb6O??In<+p>7*RwVuner3_|_kEfR(f zPPYv!QxNngG%BLtac&=j!xUxFAP_=Lmy#3YY((rn$rU0YRbVS8HU$C+#eu24scO8< zV<9jskiic{DcTS*2P*DJnwjEk#jvf6;9m(&G`SDL!rOhO^^VZu4IMB%%&XvsnNI|} zC0DV67b6^HuJ4_>fNfs%o&x7i2@F!VLXEjH3!)C3ZUA>-LrUc89ylv9@DyS&bqE~c zoxyX(9x)bSOCq}`c=pi+o43|iMaV@6Rj1ucT)OKKf}V08>h4)Fj(Eo))20vI+=*B(jXRNEKo9rOhM?T)F3N7L-OJq zkg^PzzU~0zixZba2~QZYISna9hZHAlIES9ZuIb=nhFgP(kkCnCLNH;8h(WY#XSM4G z9x76(Fv%C6f_kjs3^9OBXl@zR#u|YDmd|N7$e|2`=|n9EQy#Jr=;Q&~<_O zj2Qr6@L=yD7>0;!9qAdMP)!&u8lWe5VPGwv4dkvGfufdi+XO8XL$W*melm3RZSd67 zbO&{r2?t<02Kecg@(wWrV99CxqLe;&XPCp^uVjv|Y+E&*nT@cuc>yFB>hkWFlih_bl#ewNX%T-#gZ_aj8w0+Qr<|A9c#p-To^fg`ZkWVC6xj;24sXedC4qnf(g9ZCyc6>*o@L)ioX@q#K2D5k05tZqbbk?4-T{1pJi zK;M=K5703H7-)6Gk>gAS{IFYsAQ*h!ak$3D8I3?m24mhv=%wwCM@uAjR<yEmZLc^qpAz#}VXQ3Bnhgcg{IN&DJ z6HN)U({1gAacQBTCQxdKOl69c*2W6e0j54k$#t>TFj^?GXo?F|V$rt4ENmI88xTz0 zTsT>(#O<>Yj1ddbY);waitbq8(=2dg2?>m{kuvxYwN>%`UOE>>z-ct!XHR}55`M@; z&5+p#3yq9^N>Mnz;P8lqpy-7{kbIZ{XaZUSqr9+GG%0OlSEZhD&3WPwu$~^{4{;x( zuSKvN!&tV6wjgLbd7mlv`wAf`P^ktI5GbOWGBgF()W0%vZSYI(U?n9TU}QkNQLLyF zZCi>%*5QCmLK@m}1qvJ+GA#+F?7fF$(@bfit z45V@>3j>#NLzC~NpF+4BbEA}iEO(qbon+awVtJCxZ$WRA3U$jPs31rp_R&B^m2VT* z=)9JN+ZN!m@iDN`pg*w-K-3a`dEsbp)kEs=lX+~SWg8aYGJK$^$LJgX&YtU0%?Mu~ z;reL0F#{h>kZcFM1J8VWCgcF?Fa~{)Cd`7SV6q?Biy>i_VU`D%*+>#df%xJs?aLsL zmqOi{rDfcGeqDG``Ex%Qbq5y%Q=;@T_*Mto2grC78mK{&z4k@w7jZBpP(nXlRaeio z_#_9`9e%R{%kz-M2j3MuwGsA^TUg&2z?Cp1uWJQdx>WdFmqW@JheVwHe9J;^f&8Md zkWdN|lwMAX0%2(HP#>T4n0qls@H2*Ruzk{(W7Y{uYY>MXVej_∓o7P|O5c_WNWX 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 3 + raise(Puppet::ParseError, 'staging_parse(): Wrong number of arguments ' \ + "given (#{arguments.size} for 1, 2, 3)") + end + + source = arguments[0] + path = URI.parse(source.tr('\\', '/')).path + + if path.nil? + raise Puppet::ParseError, "staging_parse(): #{source.inspect} has no URI " \ + "'path' component" + end + + info = arguments[1] ? arguments[1] : 'filename' + extension = arguments[2] ? arguments[2] : File.extname(path) + + case info + when 'filename' + result = File.basename(path) + when 'basename' + result = File.basename(path, extension) + when 'extname' + result = File.extname(path) + when 'parent' + result = File.expand_path(File.join(path, '..')) + else + raise Puppet::ParseError, "staging_parse(), unknown parse info #{info}." + end + + return result + end +end diff --git a/environments/production/modules/staging/manifests/deploy.pp b/environments/production/modules/staging/manifests/deploy.pp new file mode 100644 index 0000000..f502e46 --- /dev/null +++ b/environments/production/modules/staging/manifests/deploy.pp @@ -0,0 +1,59 @@ +# The define resource extracts compressed file to a staging location. +define staging::deploy ( + $target, #: the target extraction directory + $source = undef, #: the source file location, supports local files, puppet://, http://, https://, ftp:// + $staging_path = undef, #: the staging location for compressed file. defaults to ${staging::path}/${caller_module_name} + $username = undef, #: https or ftp username + $certificate = undef, #: https certifcate file + $password = undef, #: https or ftp user password or https certificate password + $environment = undef, #: environment variable for settings such as http_proxy + $strip = undef, #: extract file with the --strip=X option. Only works with GNU tar. + $unzip_opts = '', #: additional options to pass the unzip command. + $timeout = undef, #: the time to wait for the file transfer to complete + $user = undef, #: extract file as this user + $group = undef, #: extract group as this group + $creates = undef, #: the file/folder created after extraction. if unspecified defaults to ${target}/${name} + $unless = undef, #: alternative way to conditionally extract file + $onlyif = undef #: alternative way to conditionally extract file +) { + + # grab file name if path was passed in + if $name =~ /.*\/(.*)/ { + $file_name = $1 + } else { + $file_name = $name + } + + if $source { + $source_path = $source + } else { + $source_path = $name + } + + staging::file { $file_name: + source => $source_path, + target => $staging_path, + username => $username, + certificate => $certificate, + password => $password, + environment => $environment, + subdir => $caller_module_name, + timeout => $timeout, + } + + staging::extract { $file_name: + target => $target, + source => $staging_path, + user => $user, + group => $group, + environment => $environment, + strip => $strip, + unzip_opts => $unzip_opts, + subdir => $caller_module_name, + creates => $creates, + unless => $unless, + onlyif => $onlyif, + require => Staging::File[$file_name], + } + +} diff --git a/environments/production/modules/staging/manifests/extract.pp b/environments/production/modules/staging/manifests/extract.pp new file mode 100644 index 0000000..6bd5490 --- /dev/null +++ b/environments/production/modules/staging/manifests/extract.pp @@ -0,0 +1,126 @@ +# Define resource to extract files from staging directories to target directories. +define staging::extract ( + $target, #: the target extraction directory + $source = undef, #: the source compression file, supports tar, tar.gz, zip, war + $creates = undef, #: the file created after extraction. if unspecified defaults ${staging::path}/${caller_module_name}/${name} ${target}/${name} + $unless = undef, #: alternative way to conditionally check whether to extract file. + $onlyif = undef, #: alternative way to conditionally check whether to extract file. + $user = undef, #: extract file as this user. + $group = undef, #: extract file as this group. + $environment = undef, #: environment variables. + $strip = undef, #: extract file with the --strip=X option. Only works with GNU tar. + $unzip_opts = '', #: additional options to pass the unzip command. + $subdir = $caller_module_name #: subdir per module in staging directory. +) { + + include ::staging + + if $source { + $source_path = $source + } else { + if $subdir == '' { # eg when $caller_module_name is blank + $source_path = "${staging::_path}/${name}" + } else { + $source_path = "${staging::_path}/${subdir}/${name}" + } + } + + # Use user supplied creates path, set default value if creates, unless or + # onlyif is not supplied. + if $creates { + $creates_path = $creates + } elsif ! ($unless or $onlyif) { + if $name =~ /.tar.gz$/ { + $folder = staging_parse($name, 'basename', '.tar.gz') + } elsif $name =~ /.tar.bz2$/ { + $folder = staging_parse($name, 'basename', '.tar.bz2') + } else { + $folder = staging_parse($name, 'basename') + } + $creates_path = "${target}/${folder}" + } else { + $creates_path = undef + } + + if scope_defaults('Exec','path') { + Exec{ + cwd => $target, + user => $user, + group => $group, + environment => $environment, + creates => $creates_path, + unless => $unless, + onlyif => $onlyif, + logoutput => on_failure, + } + } else { + Exec{ + path => $::path, + cwd => $target, + user => $user, + group => $group, + environment => $environment, + creates => $creates_path, + unless => $unless, + onlyif => $onlyif, + logoutput => on_failure, + } + } + + if $strip { + if $::osfamily == 'Solaris' or $name !~ /(.tar|.tgz|.tar.gz|.tbz2|.tar.bz2)$/ { + warning('strip is only supported with GNU tar, ignoring the parameter') + $strip_opt = '' + } else { + $strip_opt = " --strip=${strip}" + } + } else { + $strip_opt = '' + } + + case $name { + /.tar$/: { + $command = "tar xf ${source_path}${strip_opt}" + } + + /(.tgz|.tar.gz)$/: { + if $::osfamily == 'Solaris' { + $command = "gunzip -dc < ${source_path} | tar xf - " + } else { + $command = "tar xzf ${source_path}${strip_opt}" + } + } + + /(.tbz2|.tar.bz2)$/: { + $command = "tar xjf ${source_path}${strip_opt}" + } + + /.zip$/: { + $command = "unzip ${unzip_opts} ${source_path}" + } + + /(.war|.jar)$/: { + $command = "jar xf ${source_path}" + } + + /.deb$/: { + if $::osfamily == 'Debian' { + $command = "dpkg --extract ${source_path} ." + } else { + fail('The .deb filetype is only supported on Debian family systems.') + } + } + + /.Z$/: { + $command = "uncompress ${source_path}" + } + + default: { + fail("staging::extract: unsupported file format ${name}.") + } + } + + exec { "extract ${name}": + command => $command, + } +} diff --git a/environments/production/modules/staging/manifests/file.pp b/environments/production/modules/staging/manifests/file.pp new file mode 100644 index 0000000..12782ac --- /dev/null +++ b/environments/production/modules/staging/manifests/file.pp @@ -0,0 +1,166 @@ +# #### Overview: +# +# Define resource to retrieve files to staging directories. It is +# intentionally not replacing files, as these intend to be large binaries +# that are versioned. +# +# #### Notes: +# +# If you specify a different staging location, please manage the file +# resource as necessary. +# +define staging::file ( + $source, #: the source file location, supports local files, puppet://, http://, https://, ftp://, s3:// + $target = undef, #: the target file location, if unspecified ${staging::path}/${subdir}/${name} + $username = undef, #: https or ftp username + $certificate = undef, #: https certificate file + $password = undef, #: https or ftp user password or https certificate password + $environment = undef, #: environment variable for settings such as http_proxy, https_proxy, of ftp_proxy + $timeout = undef, #: the time to wait for the file transfer to complete + $curl_option = undef, #: options to pass to curl + $wget_option = undef, #: options to pass to wget + $tries = undef, #: amount of retries for the file transfer when non transient connection errors exist + $try_sleep = undef, #: time to wait between retries for the file transfer + $owner = undef, #: file owner in the local filesystem for the target + $group = undef, #: file group in the local filesystem for the target + $mode = undef, #: file mode in the local filesystem for the target + $subdir = $caller_module_name +) { + + include ::staging + + $quoted_source = shellquote($source) + + if $target { + $target_file = $target + $staging_dir = staging_parse($target, 'parent') + } else { + $staging_dir = regsubst("${staging::_path}/${subdir}", '/$', '') # Strip off trailing slashes + $target_file = "${staging_dir}/${name}" + + if ! defined(File[$staging_dir]) { + file { $staging_dir: + ensure=>directory, + } + } + } + + Exec { + path => $staging::exec_path, + environment => $environment, + cwd => $staging_dir, + creates => $target_file, + timeout => $timeout, + try_sleep => $try_sleep, + tries => $tries, + logoutput => on_failure, + } + + case $::staging_http_get { + 'curl', default: { + $quoted_credentials = shellquote("${username}:${password}") + $http_get = "curl ${curl_option} -f -L -o ${target_file} ${quoted_source}" + $http_get_passwd = "curl ${curl_option} -f -L -o ${target_file} -u ${quoted_credentials} ${quoted_source}" + $http_get_cert = "curl ${curl_option} -f -L -o ${target_file} -E ${certificate}:${password} ${quoted_source}" + $ftp_get = "curl ${curl_option} -o ${target_file} ${quoted_source}" + $ftp_get_passwd = "curl ${curl_option} -o ${target_file} -u ${username}:${password} ${quoted_source}" + } + 'wget': { + $quoted_password = shellquote($password) + $http_get = "wget ${wget_option} -O ${target_file} ${quoted_source}" + $http_get_passwd = "wget ${wget_option} -O ${target_file} --user=${username} --password=${quoted_password} ${quoted_source}" + $http_get_cert = "wget ${wget_option} -O ${target_file} --user=${username} --certificate=${certificate} ${quoted_source}" + $ftp_get = $http_get + $ftp_get_passwd = $http_get_passwd + } + 'powershell':{ + $http_get = "powershell.exe -Command \"\$wc = New-Object System.Net.WebClient;\$wc.DownloadFile('${source}','${target_file}')\"" + $ftp_get = $http_get + $http_get_passwd = "powershell.exe -Command \"\$wc = New-Object System.Net.WebClient;\$wc.Credentials = New-Object System.Net.NetworkCredential('${username}','${password}');\$wc.DownloadFile('${source}','${target_file}')\"" + $ftp_get_passwd = $http_get_passwd + } + } + + case $source { + /^\//: { + file { $target_file: + source => $source, + owner => $owner, + group => $group, + mode => $mode, + replace => false, + } + } + /^[A-Za-z]:/: { + if versioncmp($::puppetversion, '3.4.0') >= 0 { + file { $target_file: + source => $source, + owner => $owner, + group => $group, + mode => $mode, + replace => false, + source_permissions => ignore, + } + } else { + file { $target_file: + source => $source, + owner => $owner, + group => $group, + mode => $mode, + replace => false, + } + } + } + /^puppet:\/\//: { + file { $target_file: + source => $source, + owner => $owner, + group => $group, + mode => $mode, + replace => false, + } + } + /^http:\/\//: { + if $username { $command = $http_get_passwd } + else { $command = $http_get } + exec { $target_file: + command => $command, + } + } + /^https:\/\//: { + if $username { $command = $http_get_passwd } + elsif $certificate { $command = $http_get_cert } + else { $command = $http_get } + exec { $target_file: + command => $command, + } + } + /^ftp:\/\//: { + if $username { $command = $ftp_get_passwd } + else { $command = $ftp_get } + exec { $target_file: + command => $command, + } + } + /^s3:\/\//: { + $command = "aws s3 cp ${source} ${target_file}" + exec { $target_file: + command => $command, + } + } + default: { + fail("staging::file: do not recognize source ${source}.") + } + } + + if $source =~ /^(?:https?|ftp|s3):\/\// { + file { $target_file: + ensure => file, + owner => $owner, + group => $group, + mode => $mode, + require => Exec[$target_file], + } + } + +} diff --git a/environments/production/modules/staging/manifests/init.pp b/environments/production/modules/staging/manifests/init.pp new file mode 100644 index 0000000..6fac550 --- /dev/null +++ b/environments/production/modules/staging/manifests/init.pp @@ -0,0 +1,34 @@ +# This module manages staging and extraction of files from various sources. +# +# #### Actions: +# +# Creates the root staging directory. By default files will be created in a subdirectory matching the caller_module_name. +# +# /opt/staging/ +# |-- puppet +# | `-- puppet.enterprise.2.0.tar.gz +# `-- tomcat +# `-- tomcat.5.0.tar.gz +# +class staging ( + $path = $staging::params::path, #: staging directory filepath + $owner = $staging::params::owner, #: staging directory owner + $group = $staging::params::group, #: staging directory group + $mode = $staging::params::mode, #: staging directory permission + $exec_path = $staging::params::exec_path #: executable default path +) inherits staging::params { + + # Strip off any trailing '/' + $_path = regsubst($path,'/$','') + + # Resolve conflict with pe_staging + if !defined(File[$_path]) { + file { $_path: + ensure => directory, + owner => $owner, + group => $group, + mode => $mode, + } + } + +} diff --git a/environments/production/modules/staging/manifests/params.pp b/environments/production/modules/staging/manifests/params.pp new file mode 100644 index 0000000..200b3ad --- /dev/null +++ b/environments/production/modules/staging/manifests/params.pp @@ -0,0 +1,40 @@ +# OS specific parameters +class staging::params { + case $::osfamily { + default: { + $path = '/opt/staging' + $owner = '0' + $group = '0' + $mode = '0755' + $exec_path = '/usr/local/bin:/usr/bin:/bin' + } + 'Solaris': { + $path = '/opt/staging' + $owner = '0' + $group = '0' + $mode = '0755' + $exec_path = '/usr/local/bin:/usr/bin:/bin:/usr/sfw/bin' + } + 'windows': { + $path = $::staging_windir + $owner = 'S-1-5-32-544' # Adminstrators + $group = 'S-1-5-18' # SYSTEM + $mode = '0660' + $exec_path = $::path + } + 'FreeBSD': { + $path = '/var/tmp/staging' + $owner = '0' + $group = '0' + $mode = '0755' + $exec_path = '/usr/local/bin:/usr/bin:/bin' + } + 'OpenBSD': { + $path = '/var/staging' + $owner = '0' + $group = '0' + $mode = '0755' + $exec_path = '/usr/local/bin:/usr/bin:/bin' + } + } +} diff --git a/environments/production/modules/staging/metadata.json b/environments/production/modules/staging/metadata.json new file mode 100644 index 0000000..bdb4a09 --- /dev/null +++ b/environments/production/modules/staging/metadata.json @@ -0,0 +1,115 @@ +{ + "name": "puppet-staging", + "version": "2.2.0", + "author": "Vox Pupuli", + "summary": "Compressed file staging and deployment", + "license": "Apache-2.0", + "source": "https://github.com/voxpupuli/puppet-staging", + "project_page": "https://github.com/voxpupuli/puppet-staging", + "issues_url": "https://github.com/voxpupuli/puppet-staging/issues", + "dependencies": [ + + ], + "data_provider": null, + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "11 SP1" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7", + "8" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "12.04", + "14.04", + "16.04" + ] + }, + { + "operatingsystem": "Solaris", + "operatingsystemrelease": [ + "10", + "11" + ] + }, + { + "operatingsystem": "Windows", + "operatingsystemrelease": [ + "Server 2003", + "Server 2003 R2", + "Server 2008", + "Server 2008 R2", + "Server 2012", + "Server 2012 R2", + "7", + "8", + "10" + ] + }, + { + "operatingsystem": "AIX", + "operatingsystemrelease": [ + "5.3", + "6.1", + "7.1" + ] + }, + { + "operatingsystem": "OpenBSD", + "operatingsystemrelease": [ + "5.7", + "5.8", + "5.9", + "6.0" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 3.8.7 < 5.0.0" + } + ], + "description": "Manages compressed file staging and deployment." +} diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/centos-511-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/centos-511-x64.yml new file mode 100644 index 0000000..089d646 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/centos-511-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-511-x64: + roles: + - master + platform: el-5-x86_64 + box: puppetlabs/centos-5.11-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64-pe.yml b/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64-pe.yml new file mode 100644 index 0000000..1e7aea6 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64-pe.yml @@ -0,0 +1,17 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-66-x64: + roles: + - master + - database + - dashboard + platform: el-6-x86_64 + box: puppetlabs/centos-6.6-64-puppet-enterprise + hypervisor: vagrant +CONFIG: + type: pe +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64.yml new file mode 100644 index 0000000..42455e7 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/centos-66-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-66-x64: + roles: + - master + platform: el-6-x86_64 + box: puppetlabs/centos-6.6-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/centos-72-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/centos-72-x64.yml new file mode 100644 index 0000000..85af89d --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/centos-72-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-72-x64: + roles: + - master + platform: el-7-x86_64 + box: puppetlabs/centos-7.2-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/debian-78-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/debian-78-x64.yml new file mode 100644 index 0000000..6ef6de8 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/debian-78-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + debian-78-x64: + roles: + - master + platform: debian-7-amd64 + box: puppetlabs/debian-7.8-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/debian-82-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/debian-82-x64.yml new file mode 100644 index 0000000..9897a8f --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/debian-82-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + debian-82-x64: + roles: + - master + platform: debian-8-amd64 + box: puppetlabs/debian-8.2-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-5.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-5.yml new file mode 100644 index 0000000..c17bc3d --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-5.yml @@ -0,0 +1,19 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-5-x64: + platform: el-5-x86_64 + hypervisor: docker + image: centos:5 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'yum install -y crontabs initscripts iproute openssl sysvinit-tools tar wget which' + - 'sed -i -e "/mingetty/d" /etc/inittab' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-6.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-6.yml new file mode 100644 index 0000000..d93f884 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-6.yml @@ -0,0 +1,20 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-6-x64: + platform: el-6-x86_64 + hypervisor: docker + image: centos:6 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'rm -rf /var/run/network/*' + - 'yum install -y crontabs initscripts iproute openssl sysvinit-tools tar wget which' + - 'rm /etc/init/tty.conf' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-7.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-7.yml new file mode 100644 index 0000000..886b1ee --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/centos-7.yml @@ -0,0 +1,18 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + centos-7-x64: + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: '["/usr/sbin/init"]' + docker_image_commands: + - 'yum install -y crontabs initscripts iproute openssl sysvinit-tools tar wget which' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-7.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-7.yml new file mode 100644 index 0000000..071acbf --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-7.yml @@ -0,0 +1,19 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + debian-7-x64: + platform: debian-7-amd64 + hypervisor: docker + image: debian:7 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'echo deb http://ftp.debian.org/debian wheezy-backports main >> /etc/apt/sources.list' + - 'apt-get update && apt-get install -y cron locales-all net-tools wget' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-8.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-8.yml new file mode 100644 index 0000000..500bee5 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/debian-8.yml @@ -0,0 +1,20 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + debian-8-x64: + platform: debian-8-amd64 + hypervisor: docker + image: debian:8 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'echo deb http://ftp.debian.org/debian jessie-backports main >> /etc/apt/sources.list' + - 'apt-get update && apt-get install -y cron locales-all net-tools wget' + - 'rm -f /usr/sbin/policy-rc.d' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-12.04.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-12.04.yml new file mode 100644 index 0000000..ab77cda --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-12.04.yml @@ -0,0 +1,19 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-1204-x64: + platform: ubuntu-12.04-amd64 + hypervisor: docker + image: ubuntu:12.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get install -y net-tools wget' + - 'locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-14.04.yml new file mode 100644 index 0000000..54d5e5a --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-14.04.yml @@ -0,0 +1,21 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-1404-x64: + platform: ubuntu-14.04-amd64 + hypervisor: docker + image: ubuntu:14.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'rm /usr/sbin/policy-rc.d' + - 'rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl' + - 'apt-get install -y net-tools wget' + - 'locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-16.04.yml b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-16.04.yml new file mode 100644 index 0000000..92a93cb --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/docker/ubuntu-16.04.yml @@ -0,0 +1,19 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-1604-x64: + platform: ubuntu-16.04-amd64 + hypervisor: docker + image: ubuntu:16.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get install -y net-tools wget' + - 'locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 + masterless: true +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/fedora-24-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/fedora-24-x64.yml new file mode 100644 index 0000000..820b62d --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/fedora-24-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + fedora-24-x64: + roles: + - master + platform: fedora-24-x86_64 + box: fedora/24-cloud-base + hypervisor: vagrant +CONFIG: + type: aio +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/fedora-25-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/fedora-25-x64.yml new file mode 100644 index 0000000..c3a3cbf --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/fedora-25-x64.yml @@ -0,0 +1,18 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +# +# platform is fedora 24 because there is no +# puppet-agent for fedora 25 by 2016-12-30 +HOSTS: + fedora-25-x64: + roles: + - master + platform: fedora-24-x86_64 + box: fedora/25-cloud-base + hypervisor: vagrant +CONFIG: + type: aio +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1204-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1204-x64.yml new file mode 100644 index 0000000..29102c5 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1204-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-server-1204-x64: + roles: + - master + platform: ubuntu-12.04-amd64 + box: puppetlabs/ubuntu-12.04-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml new file mode 100644 index 0000000..054e658 --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-server-1404-x64: + roles: + - master + platform: ubuntu-14.04-amd64 + box: puppetlabs/ubuntu-14.04-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1604-x64.yml b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1604-x64.yml new file mode 100644 index 0000000..bc85e0e --- /dev/null +++ b/environments/production/modules/staging/spec/acceptance/nodesets/ubuntu-server-1604-x64.yml @@ -0,0 +1,15 @@ +--- +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +HOSTS: + ubuntu-server-1604-x64: + roles: + - master + platform: ubuntu-16.04-amd64 + box: puppetlabs/ubuntu-16.04-64-nocm + hypervisor: vagrant +CONFIG: + type: foss +... +# vim: syntax=yaml diff --git a/environments/production/modules/staging/spec/classes/coverage_spec.rb b/environments/production/modules/staging/spec/classes/coverage_spec.rb new file mode 100644 index 0000000..de44654 --- /dev/null +++ b/environments/production/modules/staging/spec/classes/coverage_spec.rb @@ -0,0 +1,4 @@ +require 'rspec-puppet' + +at_exit { RSpec::Puppet::Coverage.report! } +# vim: syntax=ruby diff --git a/environments/production/modules/staging/spec/default_facts.yml b/environments/production/modules/staging/spec/default_facts.yml new file mode 100644 index 0000000..13c4165 --- /dev/null +++ b/environments/production/modules/staging/spec/default_facts.yml @@ -0,0 +1,14 @@ +# This file is managed via modulesync +# https://github.com/voxpupuli/modulesync +# https://github.com/voxpupuli/modulesync_config +# +# use default_module_facts.yaml for module specific +# facts. +# +# Hint if using with rspec-puppet-facts ("on_supported_os.each"): +# if a same named fact exists in facterdb it will be overridden. +--- +concat_basedir: "/tmp" +ipaddress: "172.16.254.254" +is_pe: false +macaddress: "AA:AA:AA:AA:AA:AA" diff --git a/environments/production/modules/staging/spec/defines/staging_deploy_spec.rb b/environments/production/modules/staging/spec/defines/staging_deploy_spec.rb new file mode 100644 index 0000000..6145cf6 --- /dev/null +++ b/environments/production/modules/staging/spec/defines/staging_deploy_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' +describe 'staging::deploy', type: :define do + let(:facts) do + { + caller_module_name: '', + osfamily: 'RedHat', + staging_http_get: 'curl', + path: '/usr/local/bin:/usr/bin:/bin' + } + end + + describe 'when deploying tar.gz' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + source: 'puppet:///modules/staging/sample.tar.gz', + target: '/usr/local' + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/opt/staging') } + it { is_expected.to contain_file('/opt/staging/sample.tar.gz') } + it do + is_expected.to contain_exec('extract sample.tar.gz').with(command: 'tar xzf /opt/staging/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/usr/local', + creates: '/usr/local/sample') + end + end + + describe 'when deploying tar.gz with full path in title and no source' do + let(:title) { 'puppet:///modules/staging/sample.tar.gz' } + let(:params) do + { + target: '/usr/local' + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/opt/staging') } + it { is_expected.to contain_file('/opt/staging/sample.tar.gz') } + it do + is_expected.to contain_exec('extract sample.tar.gz').with(command: 'tar xzf /opt/staging/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/usr/local', + creates: '/usr/local/sample') + end + end + + describe 'fail when deploying tar.gz with filename in title and no source' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + target: '/usr/local' + } + end + + it do + expect do + is_expected.to contain_exec('extract sample.tar.gz') + end.to raise_error(Puppet::Error, %r{do not recognize source}) + end + end + + describe 'when deploying tar.gz with strip' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + source: 'puppet:///modules/staging/sample.tar.gz', + target: '/usr/local', + strip: 1 + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/opt/staging') } + it { is_expected.to contain_file('/opt/staging/sample.tar.gz') } + it do + is_expected.to contain_exec('extract sample.tar.gz').with(command: 'tar xzf /opt/staging/sample.tar.gz --strip=1', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/usr/local', + creates: '/usr/local/sample') + end + end + + describe 'when deploying zip file with unzip_opts' do + let(:title) { 'sample.zip' } + let(:params) do + { source: 'puppet:///modules/staging/sample.tar.gz', + target: '/usr/local', + unzip_opts: '-o -f' } + end + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/opt/staging') } + it { is_expected.to contain_file('/opt/staging/sample.zip') } + it do + is_expected.to contain_exec('extract sample.zip').with(command: 'unzip -o -f /opt/staging/sample.zip', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/usr/local', + creates: '/usr/local/sample') + end + end +end diff --git a/environments/production/modules/staging/spec/defines/staging_extract_spec.rb b/environments/production/modules/staging/spec/defines/staging_extract_spec.rb new file mode 100644 index 0000000..0adc614 --- /dev/null +++ b/environments/production/modules/staging/spec/defines/staging_extract_spec.rb @@ -0,0 +1,175 @@ +require 'spec_helper' +describe 'staging::extract', type: :define do + # forcing a more sane caller_module_name to match real usage. + let(:facts) do + { + osfamily: 'RedHat', + path: '/usr/local/bin:/usr/bin:/bin' + } + end + + describe 'when deploying tar.gz' do + let(:title) { 'sample.tar.gz' } + let(:params) { { target: '/opt' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.tar.gz').with(command: 'tar xzf /opt/staging/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying tar.gz with strip' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { target: '/opt', + strip: 1 } + end + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.tar.gz').with(command: 'tar xzf /opt/staging/sample.tar.gz --strip=1', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying tbz2' do + let(:title) { 'sample.tbz2' } + let(:params) { { target: '/opt' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.tbz2').with(command: 'tar xjf /opt/staging/sample.tbz2', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying zip' do + let(:title) { 'sample.zip' } + let(:params) { { target: '/opt' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.zip').with(command: 'unzip /opt/staging/sample.zip', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying zip with unzip_opts' do + let(:title) { 'sample.zip' } + let(:params) do + { + target: '/opt', + unzip_opts: '-o -f' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.zip').with(command: 'unzip -o -f /opt/staging/sample.zip', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying zip with strip (noop)' do + let(:title) { 'sample.zip' } + let(:params) do + { + target: '/opt', + strip: 1 + } + end + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.zip').with(command: 'unzip /opt/staging/sample.zip', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying war' do + let(:title) { 'sample.war' } + let(:params) { { target: '/opt' } } + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.war').with(command: 'jar xf /opt/staging/sample.war', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying war with strip (noop) and unzip_opts (noop)' do + let(:title) { 'sample.war' } + let(:params) do + { + target: '/opt', + strip: 1, + unzip_opts: '-o -f' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.war').with(command: 'jar xf /opt/staging/sample.war', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying deb on a Debian family system' do + let(:facts) do + { + osfamily: 'Debian', + path: '/usr/local/bin:/usr/bin:/bin' + } + end + let(:title) { 'sample.deb' } + let(:params) { { target: '/opt' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('extract sample.deb').with(command: 'dpkg --extract /opt/staging/sample.deb .', + path: '/usr/local/bin:/usr/bin:/bin', + cwd: '/opt', + creates: '/opt/sample') + end + end + + describe 'when deploying deb on a non-Debian family system' do + let(:title) { 'sample.deb' } + let(:params) do + { target: '/opt' } + end + it 'fails' do + is_expected.to compile.and_raise_error(%r{The .deb filetype is only supported on Debian family systems.}) + end + end + + describe 'when deploying unknown' do + let(:title) { 'sample.zzz' } + let(:params) { { target: '/opt' } } + + it { expect { is_expected.to contain_exec('exec sample.zzz') }.to raise_error(Puppet::Error) } + end +end diff --git a/environments/production/modules/staging/spec/defines/staging_file_spec.rb b/environments/production/modules/staging/spec/defines/staging_file_spec.rb new file mode 100644 index 0000000..4562561 --- /dev/null +++ b/environments/production/modules/staging/spec/defines/staging_file_spec.rb @@ -0,0 +1,219 @@ +require 'spec_helper' +describe 'staging::file', type: :define do + # forcing a more sane caller_module_name to match real usage. + let(:facts) do + { + osfamily: 'RedHat', + staging_http_get: 'curl', + puppetversion: Puppet.version + } + end + + describe 'when deploying via puppet' do + let(:title) { 'sample.tar.gz' } + let(:params) { { source: 'puppet:///modules/staging/sample.tar.gz' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_file('/opt/staging/sample.tar.gz') + is_expected.not_to contain_exec('/opt/staging/sample.tar.gz') + end + end + + describe 'when deploying via local' do + let(:title) { 'sample.tar.gz' } + let :params do + { + source: '/nfs/sample.tar.gz', + target: '/usr/local/sample.tar.gz' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_file('/usr/local/sample.tar.gz') + is_expected.not_to contain_exec('/opt/staging/sample.tar.gz') + end + end + + describe 'when deploying via Windows local' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + source: 'S:/nfs/sample.tar.gz', + target: '/usr/local/sample.tar.gz' + } + end + + # it { is_expected.to compile.with_all_deps } # This test will fail unless we're running the tests on Windows. + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_file('/usr/local/sample.tar.gz') + is_expected.not_to contain_exec('/opt/staging/sample.tar.gz') + end + end + + describe 'when deploying via http' do + let(:title) { 'sample.tar.gz' } + let(:params) { { source: 'http://webserver/sample.tar.gz' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -f -L -o /opt/staging/sample.tar.gz http://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + is_expected.to contain_file('/opt/staging/sample.tar.gz').with(owner: nil, + group: nil, + mode: nil) + end + end + + describe 'when deploying via http with file parameters' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + source: 'http://webserver/sample.tar.gz', + owner: 'root', + group: 'root', + mode: '0644' + } + end + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -f -L -o /opt/staging/sample.tar.gz http://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + is_expected.to contain_file('/opt/staging/sample.tar.gz').with(owner: 'root', + group: 'root', + mode: '0644') + end + end + + describe 'when deploying via http with custom curl options' do + let(:title) { 'sample.tar.gz' } + let(:params) do + { + source: 'http://webserver/sample.tar.gz', + curl_option: '-b' + } + end + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -b -f -L -o /opt/staging/sample.tar.gz http://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + end + end + + describe 'when deploying via http with parameters' do + let(:title) { 'sample.tar.gz' } + let :params do + { + source: 'http://webserver/sample.tar.gz', + target: '/usr/local/sample.tar.gz', + tries: '10', + try_sleep: '6' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/usr/local/sample.tar.gz').with(command: 'curl -f -L -o /usr/local/sample.tar.gz http://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/usr/local', + creates: '/usr/local/sample.tar.gz', + tries: '10', + try_sleep: '6') + end + end + + describe 'when deploying via https' do + let(:title) { 'sample.tar.gz' } + let(:params) { { source: 'https://webserver/sample.tar.gz' } } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_file('/opt/staging') } + it do + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -f -L -o /opt/staging/sample.tar.gz https://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + end + end + + describe 'when deploying via https with parameters' do + let(:title) { 'sample.tar.gz' } + let :params do + { + source: 'https://webserver/sample.tar.gz', + username: 'puppet', + password: 'puppet' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -f -L -o /opt/staging/sample.tar.gz -u puppet:puppet https://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + end + end + + describe 'when deploying via ftp' do + let(:title) { 'sample.tar.gz' } + let(:params) { { source: 'ftp://webserver/sample.tar.gz' } } + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -o /opt/staging/sample.tar.gz ftp://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + end + end + + describe 'when deploying via ftp with parameters' do + let(:title) { 'sample.tar.gz' } + let :params do + { + source: 'ftp://webserver/sample.tar.gz', + username: 'puppet', + password: 'puppet' + } + end + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_file('/opt/staging') + is_expected.to contain_exec('/opt/staging/sample.tar.gz').with(command: 'curl -o /opt/staging/sample.tar.gz -u puppet:puppet ftp://webserver/sample.tar.gz', + path: '/usr/local/bin:/usr/bin:/bin', + environment: nil, + cwd: '/opt/staging', + creates: '/opt/staging/sample.tar.gz', + logoutput: 'on_failure') + end + end +end diff --git a/environments/production/modules/staging/spec/fixtures/hiera.yaml b/environments/production/modules/staging/spec/fixtures/hiera.yaml new file mode 100644 index 0000000..d8adad2 --- /dev/null +++ b/environments/production/modules/staging/spec/fixtures/hiera.yaml @@ -0,0 +1,9 @@ +--- +:backends: + - puppet + +:hierarchy: + - common + +:puppet: + :datasource: data diff --git a/environments/production/modules/staging/spec/spec.opts b/environments/production/modules/staging/spec/spec.opts new file mode 100644 index 0000000..91cd642 --- /dev/null +++ b/environments/production/modules/staging/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/environments/production/modules/staging/spec/spec_helper.rb b/environments/production/modules/staging/spec/spec_helper.rb new file mode 100644 index 0000000..32709c8 --- /dev/null +++ b/environments/production/modules/staging/spec/spec_helper.rb @@ -0,0 +1,32 @@ +require 'puppetlabs_spec_helper/module_spec_helper' +require 'rspec-puppet-facts' +include RspecPuppetFacts + +if Dir.exist?(File.expand_path('../../lib', __FILE__)) && RUBY_VERSION !~ %r{^1.9} + require 'coveralls' + require 'simplecov' + require 'simplecov-console' + SimpleCov.formatters = [ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::Console, + Coveralls::SimpleCov::Formatter + ] + SimpleCov.start do + track_files 'lib/**/*.rb' + add_filter '/spec' + add_filter '/vendor' + add_filter '/.vendor' + end +end + +RSpec.configure do |c| + default_facts = { + puppetversion: Puppet.version, + facterversion: Facter.version + } + default_facts.merge!(YAML.load(File.read(File.expand_path('../default_facts.yml', __FILE__)))) if File.exist?(File.expand_path('../default_facts.yml', __FILE__)) + default_facts.merge!(YAML.load(File.read(File.expand_path('../default_module_facts.yml', __FILE__)))) if File.exist?(File.expand_path('../default_module_facts.yml', __FILE__)) + c.default_facts = default_facts +end + +# vim: syntax=ruby diff --git a/environments/production/modules/staging/spec/unit/puppet/parser/functions/scope_defaults_spec.rb b/environments/production/modules/staging/spec/unit/puppet/parser/functions/scope_defaults_spec.rb new file mode 100644 index 0000000..3c1b0e5 --- /dev/null +++ b/environments/production/modules/staging/spec/unit/puppet/parser/functions/scope_defaults_spec.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +describe 'the scope_defaults function' do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it 'exists' do + Puppet::Parser::Functions.function('scope_defaults').should == 'function_scope_defaults' + end + + it 'raises a ParseError if there is less than 2 arguments' do + expect { scope.function_scope_defaults([]) }. + to raise_error(Puppet::ParseError) + end + + it 'raises a ParseError if there is more than 2 arguments' do + expect { scope.function_scope_defaults(%w(exec path error)) }. + to raise_error(Puppet::ParseError) + end + + it 'returns false for invalid resource' do + result = scope.function_scope_defaults(%w(foo path)) + result.should(eq(false)) + end + + it 'returns false for resource without default attributes' do + if scope.respond_to? :define_settings + scope.define_settings('Exec', Puppet::Parser::Resource::Param.new(name: :path, value: '/bin')) + else + scope.setdefaults('Exec', Puppet::Parser::Resource::Param.new(name: :path, value: '/bin')) + end + result = scope.function_scope_defaults(%w(Exec foo)) + result.should(eq(false)) + end + + it 'returns true for resource with default attributes' do + if scope.respond_to? :define_settings + scope.define_settings('Exec', Puppet::Parser::Resource::Param.new(name: :path, value: '/bin')) + else + scope.setdefaults('Exec', Puppet::Parser::Resource::Param.new(name: :path, value: '/bin')) + end + result = scope.function_scope_defaults(%w(Exec path)) + result.should(eq(true)) + end +end diff --git a/environments/production/modules/staging/spec/unit/puppet/parser/functions/staging_parse_spec.rb b/environments/production/modules/staging/spec/unit/puppet/parser/functions/staging_parse_spec.rb new file mode 100644 index 0000000..100fc69 --- /dev/null +++ b/environments/production/modules/staging/spec/unit/puppet/parser/functions/staging_parse_spec.rb @@ -0,0 +1,51 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +describe 'the staging parser function' do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it 'exists' do + Puppet::Parser::Functions.function('staging_parse').should == 'function_staging_parse' + end + + it 'raises a ParseError if there is less than 1 arguments' do + -> { scope.function_staging_parse([]) }.should(raise_error(Puppet::ParseError)) + end + + it 'raises a ParseError if there is more than 3 arguments' do + -> { scope.function_staging_parse(['/etc', 'filename', '.zip', 'error']) }.should(raise_error(Puppet::ParseError)) + end + + it 'raises a ParseError if there is an invalid info request' do + -> { scope.function_staging_parse(['/etc', 'sheep', '.zip']) }.should(raise_error(Puppet::ParseError)) + end + + it "raises a ParseError if 'source' doesn't have a URI path component" do + -> { scope.function_staging_parse(['uri:without-path']) }.should(raise_error(Puppet::ParseError, %r{has no URI 'path' component})) + end + + it 'returns the filename by default' do + result = scope.function_staging_parse(['/etc/puppet/sample.tar.gz']) + result.should(eq('sample.tar.gz')) + end + + it 'returns the file basename' do + result = scope.function_staging_parse(['/etc/puppet/sample.tar.gz', 'basename']) + result.should(eq('sample.tar')) + end + + it 'returns the file basename with custom extensions' do + result = scope.function_staging_parse(['/etc/puppet/sample.tar.gz', 'basename', '.tar.gz']) + result.should(eq('sample')) + end + + it 'returns the file extname' do + result = scope.function_staging_parse(['/etc/puppet/sample.tar.gz', 'extname']) + result.should(eq('.gz')) + end + + it 'returns the file parent' do + result = scope.function_staging_parse(['/etc/puppet/sample.tar.gz', 'parent']) + result.should(eq('/etc/puppet')) + end +end diff --git a/environments/production/modules/stdlib/CHANGELOG.md b/environments/production/modules/stdlib/CHANGELOG.md new file mode 100644 index 0000000..eeca835 --- /dev/null +++ b/environments/production/modules/stdlib/CHANGELOG.md @@ -0,0 +1,775 @@ +## Supported Release 4.16.0 +### Summary + +This release sees a massive update to all unit tests to test UTF8 characters. There are also multiple cleanups in preparation for internationalization. Alongside this, improvements to ipv6 support, a new length function compatible with Puppet 4, and an update to path types. Also contains multiple bug fixes around functionality and tests. + +#### Features +- Addition of coverage in all unit tests for functions, data and resource types for UTF8 for i18n. +- All strings within the readme and functions that are split over two lines have been combined in preparation for i18n parser/decorator. +- Improvement on the ipv6 support for type - Improves regex to catch some valid (but lesser known) ipv6 strings, mostly those which are a mix of ipv6 strings and embedded ipv6 numbers. +- Adds a new parameter `encoding` to allow non UTF-8 files to specify a file encoding. This prevents receiving the error message "invalid byte sequence in UTF-8" when special characters that are not UTF-8 encoded appear in the input stream, such as the copyright symbol. +- Addition of the new length function. Returns the length of a given string, array or hash. To eventually replace the deprecated size() function as can handle the new type functionality introduced in Puppet 4. +- Permit double slash in absolute/Unix path types. + +#### Bugfixes +- Fix unsupported data type error with rspec-puppet master. +- Now allows test module metadata.json to be read by Puppet. +- Fix acceptance test failure "Hiera is not a class". +- Removal of unsupported platforms and future parser setting in acceptance tests. +- Regex for tuple checking has been loosened. +- Ensure_packages function - Now only tries to apply the resource if not defined. +- (MODULES-4528) Use versioncmp to check Puppet version for 4.10.x compat. +- Adds comments to warn for UTF8 incompatibility of the functions that may not be compatible with UTF8 with Ruby < 2.4.0. + +## Supported Release 4.15.0 +### Summary + +This release introduces multiple new functions, a new fact and the addition of Ubuntu Xenial support. Also includes a bugfix and documentation update. + +#### Features +- Addition of puppet_server fact to return agents server. +- Addition of a pry function. +- Addition of tests for ensure_resources. +- Addition of FQDN UUID generation function. +- Addition of Ubuntu Xenial to OS Support. + +####Bugfixes +- Ensure_packages now works with Ruby < 2.0. +- Updated the documentation of str2bool function. + +## Supported Release 4.14.0 +### Summary + +Adds several new features and updates, especially around refining the deprecation and validate_legacy functions. Also includes a Gemfile update around an issue with parallel_tests dependancy for different versions of Ruby. + +#### Features +- Deprecation function now uses puppet stacktrace if available. +- join_key_to_values function now handles array values. If values are arrays, multiple keys are added for each element. +- Updated Gemfile to deal with parallel_tests Ruby dependancy (MODULES-3983). +- Updated/Fixed ipv4 regex validator (MODULES-3980). +- Deprecation clarification added to README. + +#### Bugfixes +- README typo fixes. +- Use .dup to duplicate classes for modification (MODULES-3829). +- Fixes spec failures that were caused by a change in the tested error message in validate_legacy_spec. +- Broken link to validate_legacy docs fixed. +- Updates deprecation tests to include future parser. + +## Supported Release 4.13.1 +### Summary + +This bugfix release addresses the `undefined method 'optional_repeated_param'` error messages seen by users of puppet 3.7. + +It also improves the user experience around function deprecations by emitting one warning per function(-name) instead of only one deprecation overall. This allows users to identify all deprecated functions used in one agent run, with less back-and-forth. + +#### Bugfixes + +* Emit deprecations warnings for each function, instead of once per process. (MODULES-3961) +* Use a universally available API for the v4 deprecation stubs of `is_*` and `validate_*`. (MODULES-3962) +* Make `getvar()` compatible to ruby 1.8.7. (MODULES-3969) +* Add v4 deprecation stubs for the `is_` counterparts of the deprecated functions to emit the deprecations warnings in all cases. + + +## Supported Release 4.13.0 +### Summary + +This version of stdlib deprecates a whole host of functions, and provides stepping stones to move to Puppet 4 type validations. Be sure to check out the new `deprecation()` and `validate_legacy()` functions to migrate off the deprecated v3-style data validations. + +Many thanks to all community contributors: bob, Dmitry Ilyin, Dominic Cleal, Joris, Joseph Yaworski, Loic Antoine-Gombeaud, Maksym Melnychok, Michiel Brandenburg, Nate Potter, Romain Tartière, Stephen Benjamin, and Steve Moore, as well as anyone contributing in the code review process and by submitting issues. + +Special thanks to [Voxpupuli's](https://voxpupuli.org/) Igor Galić for donating the puppet-tea types to kickstart this part of stdlib. + + +#### Deprecations +* `validate_absolute_path`, `validate_array`, `validate_bool`, `validate_hash`, `validate_integer`, `validate_ip_address`, `validate_ipv4_address`, `validate_ipv6_address`, `validate_numeric`, `validate_re`, `validate_slength`, `validate_string`, and their `is_` counter parts are now deprecated on Puppet 4. See the `validate_legacy()` description in the README for help on migrating away from those functions. +* The `dig` function is provided by core puppet since 4.5.0 with slightly different calling convention. The stdlib version can still be accessed as `dig44` for now. + + +#### Features +* Add Puppet 4 data types for Unix, and Windows paths, and URLs. +* Add `deprecation` function to warn users of functionality that will be removed soon. +* Add `validate_legacy` function to help with migrating to Puppet 4 data types. + +* Add `any2bool` function, a combination of of `string2bool` and `num2bool`. +* Add `delete_regex` function to delete array elements matching a regular expression. +* Add `puppet_environmentpath` fact to expose the `environmentpath` setting. +* Add `regexpescape` function to safely insert arbitrary strings into regular expressions. +* Add `shell_escape`, `shell_join`, and `shell_split` functions for safer working with shell scripts.. + +* The `delete` function now also accepts regular expressions as search term. +* The `loadyaml` function now accepts a default value, which is returned when there is an error loading the file. + +#### Bugfixes +* Fix `file_line.match_for_absence` implementation and description to actually work. (MODULES-3590) +* Fix `getparam` so that it can now also return `false`. (MODULES-3933) +* Fix the fixture setup for testing and adjust `load_module_metadata` and `loadjson` tests. +* Fix `defined_with_params` to handle `undef` correctly on all puppet versions. (PUP-6422, MODULES-3543) +* Fix `file_line.path` validation to use puppet's built in `absolute_path?` matcher. + +#### Minor Improvements +* README changes: improved descriptions of `deep_merge`, `delete`, `ensure_packages`, `file_line.after`, `range`, and `validate_numeric`. +* The `getvar` function now returns nil in all situations where the variable is not found. +* Update the `dig44` function with better `undef`, `nil`, and `false` handling. +* Better wording on `str2bool` argument validation error message. + + +### Known issues +* The `validate_legacy` function relies on internal APIs from Puppet 4.4.0 (PE 2016.1) onwards, and doesn't work on earlier versions. +* Puppet 4.5.0 (PE 2016.2) has a number of improvements around data types - especially error handling - that make working with them much nicer. + +## Supported Release 4.12.0 +###Summary + +This release provides several new functions, bugfixes, modulesync changes, and some documentation updates. + +####Features +- Adds `clamp`. This function keeps values within a specified range. +- Adds `validate_x509_rsa_key_pair`. This function validates an x509 RSA certificate and key pair. +- Adds `dig`. This function performs a deep lookup in nested hashes or arrays. +- Extends the `base64` support to fit `rfc2045` and `rfc4648`. +- Adds `is_ipv6_address` and `is_ipv4_address`. These functions validate the specified ipv4 or ipv6 addresses. +- Adds `enclose_ipv6`. This function encloses IPv6 addresses in square brackets. +- Adds `ensure_resources`. This function takes a list of resources and creates them if they do not exist. +- Extends `suffix` to support applying a suffix to keys in a hash. +- Apply modulesync changes. +- Add validate_email_address function. + +####Bugfixes +- Fixes `fqdn_rand_string` tests, since Puppet 4.4.0 and later have a higher `fqdn_rand` ceiling. +- (MODULES-3152) Adds a check to `package_provider` to prevent failures if Gem is not installed. +- Fixes to README.md. +- Fixes catch StandardError rather than the gratuitous Exception +- Fixes file_line attribute validation. +- Fixes concat with Hash arguments. + +## Supported Release 4.11.0 +###Summary + +Provides a validate_absolute_paths and Debian 8 support. There is a fix to the is_package_provider fact and a test improvement. + +####Features +- Adds new parser called is_absolute_path +- Supports Debian 8 + +####Bugfixes +- Allow package_provider fact to resolve on PE 3.x + +####Improvements +- ensures that the test passes independently of changes to rubygems for ensure_resource + +##2015-12-15 - Supported Release 4.10.0 +###Summary + +Includes the addition of several new functions and considerable improvements to the existing functions, tests and documentation. Includes some bug fixes which includes compatibility, test and fact issues. + +####Features +- Adds service_provider fact +- Adds is_a() function +- Adds package_provider fact +- Adds validate_ip_address function +- Adds seeded_rand function + +####Bugfixes +- Fix backwards compatibility from an improvement to the parseyaml function +- Renaming of load_module_metadata test to include _spec.rb +- Fix root_home fact on AIX 5.x, now '-c' rather than '-C' +- Fixed Gemfile to work with ruby 1.8.7 + +####Improvements +- (MODULES-2462) Improvement of parseyaml function +- Improvement of str2bool function +- Improvement to readme +- Improvement of intersection function +- Improvement of validate_re function +- Improved speed on Facter resolution of service_provider +- empty function now handles numeric values +- Package_provider now prevents deprecation warning about the allow_virtual parameter +- load_module_metadata now succeeds on empty file +- Check added to ensure puppetversion value is not nil +- Improvement to bool2str to return a string of choice using boolean +- Improvement to naming convention in validate_ipv4_address function + +## Supported Release 4.9.1 +###Summary + +Small release for support of newer PE versions. This increments the version of PE in the metadata.json file. + +##2015-09-08 - Supported Release 4.9.0 +###Summary + +This release adds new features including the new functions dos2unix, unix2dos, try_get_value, convert_base as well as other features and improvements. + +####Features +- (MODULES-2370) allow `match` parameter to influence `ensure => absent` behavior +- (MODULES-2410) Add new functions dos2unix and unix2dos +- (MODULE-2456) Modify union to accept more than two arrays +- Adds a convert_base function, which can convert numbers between bases +- Add a new function "try_get_value" + +####Bugfixes +- n/a + +####Improvements +- (MODULES-2478) Support root_home fact on AIX through "lsuser" command +- Acceptance test improvements +- Unit test improvements +- Readme improvements + +## 2015-08-10 - Supported Release 4.8.0 +### Summary +This release adds a function for reading metadata.json from any module, and expands file\_line's abilities. + +#### Features +- New parameter `replace` on `file_line` +- New function `load_module_metadata()` to load metadata.json and return the content as a hash. +- Added hash support to `size()` + +#### Bugfixes +- Fix various docs typos +- Fix `file_line` resource on puppet < 3.3 + +##2015-06-22 - Supported Release 4.7.0 +###Summary + +Adds Solaris 12 support along with improved Puppet 4 support. There are significant test improvements, and some minor fixes. + +####Features +- Add support for Solaris 12 + +####Bugfixes +- Fix for AIO Puppet 4 +- Fix time for ruby 1.8.7 +- Specify rspec-puppet version +- range() fix for typeerror and missing functionality +- Fix pw_hash() on JRuby < 1.7.17 +- fqdn_rand_string: fix argument error message +- catch and rescue from looking up non-existent facts +- Use puppet_install_helper, for Puppet 4 + +####Improvements +- Enforce support for Puppet 4 testing +- fqdn_rotate/fqdn_rand_string acceptance tests and implementation +- Simplify mac address regex +- validate_integer, validate_numeric: explicitely reject hashes in arrays +- Readme edits +- Remove all the pops stuff for rspec-puppet +- Sync via modulesync +- Add validate_slength optional 3rd arg +- Move tests directory to examples directory + +##2015-04-14 - Supported Release 4.6.0 +###Summary + +Adds functions and function argument abilities, and improves compatibility with the new puppet parser + +####Features +- MODULES-444: `concat()` can now take more than two arrays +- `basename()` added to have Ruby File.basename functionality +- `delete()` can now take an array of items to remove +- `prefix()` can now take a hash +- `upcase()` can now take a hash or array of upcaseable things +- `validate_absolute_path()` can now take an array +- `validate_cmd()` can now use % in the command to embed the validation file argument in the string +- MODULES-1473: deprecate `type()` function in favor of `type3x()` +- MODULES-1473: Add `type_of()` to give better type information on future parser +- Deprecate `private()` for `assert_private()` due to future parser +- Adds `ceiling()` to take the ceiling of a number +- Adds `fqdn_rand_string()` to generate random string based on fqdn +- Adds `pw_hash()` to generate password hashes +- Adds `validate_integer()` +- Adds `validate_numeric()` (like `validate_integer()` but also accepts floats) + +####Bugfixes +- Fix seeding of `fqdn_rotate()` +- `ensure_resource()` is more verbose on debug mode +- Stricter argument checking for `dirname()` +- Fix `is_domain_name()` to better match RFC +- Fix `uriescape()` when called with array +- Fix `file_line` resource when using the `after` attribute with `match` + +##2015-01-14 - Supported Release 4.5.1 +###Summary + +This release changes the temporary facter_dot_d cache locations outside of the /tmp directory due to a possible security vunerability. CVE-2015-1029 + +####Bugfixes +- Facter_dot_d cache will now be stored in puppet libdir instead of tmp + +##2014-12-15 - Supported Release 4.5.0 +###Summary + +This release improves functionality of the member function and adds improved future parser support. + +####Features +- MODULES-1329: Update member() to allow the variable to be an array. +- Sync .travis.yml, Gemfile, Rakefile, and CONTRIBUTING.md via modulesync + +####Bugfixes +- Fix range() to work with numeric ranges with the future parser +- Accurately express SLES support in metadata.json (was missing 10SP4 and 12) +- Don't require `line` to match the `match` parameter + +##2014-11-10 - Supported Release 4.4.0 +###Summary +This release has an overhauled readme, new private manifest function, and fixes many future parser bugs. + +####Features +- All new shiny README +- New `private()` function for making private manifests (yay!) + +####Bugfixes +- Code reuse in `bool2num()` and `zip()` +- Fix many functions to handle `generate()` no longer returning a string on new puppets +- `concat()` no longer modifies the first argument (whoops) +- strict variable support for `getvar()`, `member()`, `values_at`, and `has_interface_with()` +- `to_bytes()` handles PB and EB now +- Fix `tempfile` ruby requirement for `validate_augeas()` and `validate_cmd()` +- Fix `validate_cmd()` for windows +- Correct `validate_string()` docs to reflect non-handling of `undef` +- Fix `file_line` matching on older rubies + + +##2014-07-15 - Supported Release 4.3.2 +###Summary + +This release merely updates metadata.json so the module can be uninstalled and +upgraded via the puppet module command. + +##2014-07-14 - Supported Release 4.3.1 +### Summary +This supported release updates the metadata.json to work around upgrade behavior of the PMT. + +#### Bugfixes +- Synchronize metadata.json with PMT-generated metadata to pass checksums + +##2014-06-27 - Supported Release 4.3.0 +### Summary +This release is the first supported release of the stdlib 4 series. It remains +backwards-compatible with the stdlib 3 series. It adds two new functions, one bugfix, and many testing updates. + +#### Features +- New `bool2str()` function +- New `camelcase()` function + +#### Bugfixes +- Fix `has_interface_with()` when interfaces fact is nil + +##2014-06-04 - Release 4.2.2 +### Summary + +This release adds PE3.3 support in the metadata and fixes a few tests. + +## 2014-05-08 - Release - 4.2.1 +### Summary +This release moves a stray symlink that can cause problems. + +## 2014-05-08 - Release - 4.2.0 +### Summary +This release adds many new functions and fixes, and continues to be backwards compatible with stdlib 3.x + +#### Features +- New `base64()` function +- New `deep_merge()` function +- New `delete_undef_values()` function +- New `delete_values()` function +- New `difference()` function +- New `intersection()` function +- New `is_bool()` function +- New `pick_default()` function +- New `union()` function +- New `validate_ipv4_address` function +- New `validate_ipv6_address` function +- Update `ensure_packages()` to take an option hash as a second parameter. +- Update `range()` to take an optional third argument for range step +- Update `validate_slength()` to take an optional third argument for minimum length +- Update `file_line` resource to take `after` and `multiple` attributes + +#### Bugfixes +- Correct `is_string`, `is_domain_name`, `is_array`, `is_float`, and `is_function_available` for parsing odd types such as bools and hashes. +- Allow facts.d facts to contain `=` in the value +- Fix `root_home` fact on darwin systems +- Fix `concat()` to work with a second non-array argument +- Fix `floor()` to work with integer strings +- Fix `is_integer()` to return true if passed integer strings +- Fix `is_numeric()` to return true if passed integer strings +- Fix `merge()` to work with empty strings +- Fix `pick()` to raise the correct error type +- Fix `uriescape()` to use the default URI.escape list +- Add/update unit & acceptance tests. + + +##2014-03-04 - Supported Release - 3.2.1 +###Summary +This is a supported release + +####Bugfixes +- Fixed `is_integer`/`is_float`/`is_numeric` for checking the value of arithmatic expressions. + +####Known bugs +* No known bugs + +--- + +##### 2013-05-06 - Jeff McCune - 4.1.0 + + * (#20582) Restore facter\_dot\_d to stdlib for PE users (3b887c8) + * (maint) Update Gemfile with GEM\_FACTER\_VERSION (f44d535) + +##### 2013-05-06 - Alex Cline - 4.1.0 + + * Terser method of string to array conversion courtesy of ethooz. (d38bce0) + +##### 2013-05-06 - Alex Cline 4.1.0 + + * Refactor ensure\_resource expectations (b33cc24) + +##### 2013-05-06 - Alex Cline 4.1.0 + + * Changed str-to-array conversion and removed abbreviation. (de253db) + +##### 2013-05-03 - Alex Cline 4.1.0 + + * (#20548) Allow an array of resource titles to be passed into the ensure\_resource function (e08734a) + +##### 2013-05-02 - Raphaël Pinson - 4.1.0 + + * Add a dirname function (2ba9e47) + +##### 2013-04-29 - Mark Smith-Guerrero - 4.1.0 + + * (maint) Fix a small typo in hash() description (928036a) + +##### 2013-04-12 - Jeff McCune - 4.0.2 + + * Update user information in gemspec to make the intent of the Gem clear. + +##### 2013-04-11 - Jeff McCune - 4.0.1 + + * Fix README function documentation (ab3e30c) + +##### 2013-04-11 - Jeff McCune - 4.0.0 + + * stdlib 4.0 drops support with Puppet 2.7 + * stdlib 4.0 preserves support with Puppet 3 + +##### 2013-04-11 - Jeff McCune - 4.0.0 + + * Add ability to use puppet from git via bundler (9c5805f) + +##### 2013-04-10 - Jeff McCune - 4.0.0 + + * (maint) Make stdlib usable as a Ruby GEM (e81a45e) + +##### 2013-04-10 - Erik Dalén - 4.0.0 + + * Add a count function (f28550e) + +##### 2013-03-31 - Amos Shapira - 4.0.0 + + * (#19998) Implement any2array (7a2fb80) + +##### 2013-03-29 - Steve Huff - 4.0.0 + + * (19864) num2bool match fix (8d217f0) + +##### 2013-03-20 - Erik Dalén - 4.0.0 + + * Allow comparisons of Numeric and number as String (ff5dd5d) + +##### 2013-03-26 - Richard Soderberg - 4.0.0 + + * add suffix function to accompany the prefix function (88a93ac) + +##### 2013-03-19 - Kristof Willaert - 4.0.0 + + * Add floor function implementation and unit tests (0527341) + +##### 2012-04-03 - Eric Shamow - 4.0.0 + + * (#13610) Add is\_function\_available to stdlib (961dcab) + +##### 2012-12-17 - Justin Lambert - 4.0.0 + + * str2bool should return a boolean if called with a boolean (5d5a4d4) + +##### 2012-10-23 - Uwe Stuehler - 4.0.0 + + * Fix number of arguments check in flatten() (e80207b) + +##### 2013-03-11 - Jeff McCune - 4.0.0 + + * Add contributing document (96e19d0) + +##### 2013-03-04 - Raphaël Pinson - 4.0.0 + + * Add missing documentation for validate\_augeas and validate\_cmd to README.markdown (a1510a1) + +##### 2013-02-14 - Joshua Hoblitt - 4.0.0 + + * (#19272) Add has\_element() function (95cf3fe) + +##### 2013-02-07 - Raphaël Pinson - 4.0.0 + + * validate\_cmd(): Use Puppet::Util::Execution.execute when available (69248df) + +##### 2012-12-06 - Raphaël Pinson - 4.0.0 + + * Add validate\_augeas function (3a97c23) + +##### 2012-12-06 - Raphaël Pinson - 4.0.0 + + * Add validate\_cmd function (6902cc5) + +##### 2013-01-14 - David Schmitt - 4.0.0 + + * Add geppetto project definition (b3fc0a3) + +##### 2013-01-02 - Jaka Hudoklin - 4.0.0 + + * Add getparam function to get defined resource parameters (20e0e07) + +##### 2013-01-05 - Jeff McCune - 4.0.0 + + * (maint) Add Travis CI Support (d082046) + +##### 2012-12-04 - Jeff McCune - 4.0.0 + + * Clarify that stdlib 3 supports Puppet 3 (3a6085f) + +##### 2012-11-30 - Erik Dalén - 4.0.0 + + * maint: style guideline fixes (7742e5f) + +##### 2012-11-09 - James Fryman - 4.0.0 + + * puppet-lint cleanup (88acc52) + +##### 2012-11-06 - Joe Julian - 4.0.0 + + * Add function, uriescape, to URI.escape strings. Redmine #17459 (fd52b8d) + +##### 2012-09-18 - Chad Metcalf - 3.2.0 + + * Add an ensure\_packages function. (8a8c09e) + +##### 2012-11-23 - Erik Dalén - 3.2.0 + + * (#17797) min() and max() functions (9954133) + +##### 2012-05-23 - Peter Meier - 3.2.0 + + * (#14670) autorequire a file\_line resource's path (dfcee63) + +##### 2012-11-19 - Joshua Harlan Lifton - 3.2.0 + + * Add join\_keys\_to\_values function (ee0f2b3) + +##### 2012-11-17 - Joshua Harlan Lifton - 3.2.0 + + * Extend delete function for strings and hashes (7322e4d) + +##### 2012-08-03 - Gary Larizza - 3.2.0 + + * Add the pick() function (ba6dd13) + +##### 2012-03-20 - Wil Cooley - 3.2.0 + + * (#13974) Add predicate functions for interface facts (f819417) + +##### 2012-11-06 - Joe Julian - 3.2.0 + + * Add function, uriescape, to URI.escape strings. Redmine #17459 (70f4a0e) + +##### 2012-10-25 - Jeff McCune - 3.1.1 + + * (maint) Fix spec failures resulting from Facter API changes (97f836f) + +##### 2012-10-23 - Matthaus Owens - 3.1.0 + + * Add PE facts to stdlib (cdf3b05) + +##### 2012-08-16 - Jeff McCune - 3.0.1 + + * Fix accidental removal of facts\_dot\_d.rb in 3.0.0 release + +##### 2012-08-16 - Jeff McCune - 3.0.0 + + * stdlib 3.0 drops support with Puppet 2.6 + * stdlib 3.0 preserves support with Puppet 2.7 + +##### 2012-08-07 - Dan Bode - 3.0.0 + + * Add function ensure\_resource and defined\_with\_params (ba789de) + +##### 2012-07-10 - Hailee Kenney - 3.0.0 + + * (#2157) Remove facter\_dot\_d for compatibility with external facts (f92574f) + +##### 2012-04-10 - Chris Price - 3.0.0 + + * (#13693) moving logic from local spec\_helper to puppetlabs\_spec\_helper (85f96df) + +##### 2012-10-25 - Jeff McCune - 2.5.1 + + * (maint) Fix spec failures resulting from Facter API changes (97f836f) + +##### 2012-10-23 - Matthaus Owens - 2.5.0 + + * Add PE facts to stdlib (cdf3b05) + +##### 2012-08-15 - Dan Bode - 2.5.0 + + * Explicitly load functions used by ensure\_resource (9fc3063) + +##### 2012-08-13 - Dan Bode - 2.5.0 + + * Add better docs about duplicate resource failures (97d327a) + +##### 2012-08-13 - Dan Bode - 2.5.0 + + * Handle undef for parameter argument (4f8b133) + +##### 2012-08-07 - Dan Bode - 2.5.0 + + * Add function ensure\_resource and defined\_with\_params (a0cb8cd) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * Disable tests that fail on 2.6.x due to #15912 (c81496e) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * (Maint) Fix mis-use of rvalue functions as statements (4492913) + +##### 2012-08-20 - Jeff McCune - 2.5.0 + + * Add .rspec file to repo root (88789e8) + +##### 2012-06-07 - Chris Price - 2.4.0 + + * Add support for a 'match' parameter to file\_line (a06c0d8) + +##### 2012-08-07 - Erik Dalén - 2.4.0 + + * (#15872) Add to\_bytes function (247b69c) + +##### 2012-07-19 - Jeff McCune - 2.4.0 + + * (Maint) use PuppetlabsSpec::PuppetInternals.scope (master) (deafe88) + +##### 2012-07-10 - Hailee Kenney - 2.4.0 + + * (#2157) Make facts\_dot\_d compatible with external facts (5fb0ddc) + +##### 2012-03-16 - Steve Traylen - 2.4.0 + + * (#13205) Rotate array/string randomley based on fqdn, fqdn\_rotate() (fef247b) + +##### 2012-05-22 - Peter Meier - 2.3.3 + + * fix regression in #11017 properly (f0a62c7) + +##### 2012-05-10 - Jeff McCune - 2.3.3 + + * Fix spec tests using the new spec\_helper (7d34333) + +##### 2012-05-10 - Puppet Labs - 2.3.2 + + * Make file\_line default to ensure => present (1373e70) + * Memoize file\_line spec instance variables (20aacc5) + * Fix spec tests using the new spec\_helper (1ebfa5d) + * (#13595) initialize\_everything\_for\_tests couples modules Puppet ver (3222f35) + * (#13439) Fix MRI 1.9 issue with spec\_helper (15c5fd1) + * (#13439) Fix test failures with Puppet 2.6.x (665610b) + * (#13439) refactor spec helper for compatibility with both puppet 2.7 and master (82194ca) + * (#13494) Specify the behavior of zero padded strings (61891bb) + +##### 2012-03-29 Puppet Labs - 2.1.3 + +* (#11607) Add Rakefile to enable spec testing +* (#12377) Avoid infinite loop when retrying require json + +##### 2012-03-13 Puppet Labs - 2.3.1 + +* (#13091) Fix LoadError bug with puppet apply and puppet\_vardir fact + +##### 2012-03-12 Puppet Labs - 2.3.0 + +* Add a large number of new Puppet functions +* Backwards compatibility preserved with 2.2.x + +##### 2011-12-30 Puppet Labs - 2.2.1 + +* Documentation only release for the Forge + +##### 2011-12-30 Puppet Labs - 2.1.2 + +* Documentation only release for PE 2.0.x + +##### 2011-11-08 Puppet Labs - 2.2.0 + +* #10285 - Refactor json to use pson instead. +* Maint - Add watchr autotest script +* Maint - Make rspec tests work with Puppet 2.6.4 +* #9859 - Add root\_home fact and tests + +##### 2011-08-18 Puppet Labs - 2.1.1 + +* Change facts.d paths to match Facter 2.0 paths. +* /etc/facter/facts.d +* /etc/puppetlabs/facter/facts.d + +##### 2011-08-17 Puppet Labs - 2.1.0 + +* Add R.I. Pienaar's facts.d custom facter fact +* facts defined in /etc/facts.d and /etc/puppetlabs/facts.d are + automatically loaded now. + +##### 2011-08-04 Puppet Labs - 2.0.0 + +* Rename whole\_line to file\_line +* This is an API change and as such motivating a 2.0.0 release according to semver.org. + +##### 2011-08-04 Puppet Labs - 1.1.0 + +* Rename append\_line to whole\_line +* This is an API change and as such motivating a 1.1.0 release. + +##### 2011-08-04 Puppet Labs - 1.0.0 + +* Initial stable release +* Add validate\_array and validate\_string functions +* Make merge() function work with Ruby 1.8.5 +* Add hash merging function +* Add has\_key function +* Add loadyaml() function +* Add append\_line native + +##### 2011-06-21 Jeff McCune - 0.1.7 + +* Add validate\_hash() and getvar() functions + +##### 2011-06-15 Jeff McCune - 0.1.6 + +* Add anchor resource type to provide containment for composite classes + +##### 2011-06-03 Jeff McCune - 0.1.5 + +* Add validate\_bool() function to stdlib + +##### 0.1.4 2011-05-26 Jeff McCune + +* Move most stages after main + +##### 0.1.3 2011-05-25 Jeff McCune + +* Add validate\_re() function + +##### 0.1.2 2011-05-24 Jeff McCune + +* Update to add annotated tag + +##### 0.1.1 2011-05-24 Jeff McCune + +* Add stdlib::stages class with a standard set of stages diff --git a/environments/production/modules/stdlib/CONTRIBUTING.md b/environments/production/modules/stdlib/CONTRIBUTING.md new file mode 100644 index 0000000..990edba --- /dev/null +++ b/environments/production/modules/stdlib/CONTRIBUTING.md @@ -0,0 +1,217 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% bundle exec rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% bundle exec rake spec_clean +% bundle exec rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppet.com/community/get-help) + +* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) diff --git a/environments/production/modules/stdlib/Gemfile b/environments/production/modules/stdlib/Gemfile new file mode 100644 index 0000000..5d86325 --- /dev/null +++ b/environments/production/modules/stdlib/Gemfile @@ -0,0 +1,86 @@ +#This file is generated by ModuleSync, do not edit. + +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +# Determines what type of gem is requested based on place_or_version. +def gem_type(place_or_version) + if place_or_version =~ /^git:/ + :git + elsif place_or_version =~ /^file:/ + :file + else + :gem + end +end + +# Find a location or specific version for a gem. place_or_version can be a +# version, which is most often used. It can also be git, which is specified as +# `git://somewhere.git#branch`. You can also use a file source location, which +# is specified as `file://some/location/on/disk`. +def location_for(place_or_version, fake_version = nil) + if place_or_version =~ /^(git[:@][^#]*)#(.*)/ + [fake_version, { :git => $1, :branch => $2, :require => false }].compact + elsif place_or_version =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [place_or_version, { :require => false }] + end +end + +# Used for gem conditionals +supports_windows = false + +group :development do + gem 'puppet-lint', :require => false + gem 'metadata-json-lint', :require => false, :platforms => 'ruby' + gem 'puppet_facts', :require => false + gem 'puppet-blacksmith', '>= 3.4.0', :require => false, :platforms => 'ruby' + gem 'puppetlabs_spec_helper', '>= 1.2.1', :require => false + gem 'rspec-puppet', '>= 2.3.2', :require => false + gem 'rspec-puppet-facts', :require => false, :platforms => 'ruby' + gem 'mocha', '< 1.2.0', :require => false + gem 'simplecov', :require => false, :platforms => 'ruby' + gem 'parallel_tests', '< 2.10.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'parallel_tests', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0') + gem 'rubocop', '0.41.2', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'rubocop', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0') + gem 'rubocop-rspec', '~> 1.6', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0') + gem 'pry', :require => false + gem 'json_pure', '<= 2.0.1', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') + gem 'fast_gettext', '1.1.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0') + gem 'fast_gettext', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0') + gem 'rainbow', '< 2.2.0', :require => false +end + +group :system_tests do + gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '>= 3') + gem 'beaker-pe', :require => false + gem 'beaker-rspec', *location_for(ENV['BEAKER_RSPEC_VERSION']) + gem 'beaker-puppet_install_helper', :require => false + gem 'beaker-module_install_helper', :require => false + gem 'master_manipulator', :require => false + gem 'beaker-hostgenerator', *location_for(ENV['BEAKER_HOSTGENERATOR_VERSION']) + gem 'beaker-abs', *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.1') +end + +gem 'puppet', *location_for(ENV['PUPPET_GEM_VERSION']) + +# Only explicitly specify Facter/Hiera if a version has been specified. +# Otherwise it can lead to strange bundler behavior. If you are seeing weird +# gem resolution behavior, try setting `DEBUG_RESOLVER` environment variable +# to `1` and then run bundle install. +gem 'facter', *location_for(ENV['FACTER_GEM_VERSION']) if ENV['FACTER_GEM_VERSION'] +gem 'hiera', *location_for(ENV['HIERA_GEM_VERSION']) if ENV['HIERA_GEM_VERSION'] + + +# Evaluate Gemfile.local if it exists +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end + +# Evaluate ~/.gemfile if it exists +if File.exists?(File.join(Dir.home, '.gemfile')) + eval(File.read(File.join(Dir.home, '.gemfile')), binding) +end + +# vim:ft=ruby diff --git a/environments/production/modules/stdlib/LICENSE b/environments/production/modules/stdlib/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/environments/production/modules/stdlib/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/environments/production/modules/stdlib/MAINTAINERS.md b/environments/production/modules/stdlib/MAINTAINERS.md new file mode 100644 index 0000000..e8004a5 --- /dev/null +++ b/environments/production/modules/stdlib/MAINTAINERS.md @@ -0,0 +1,6 @@ +## Maintenance + +Maintainers: + - Puppet Forge Modules Team `forge-modules |at| puppet |dot| com` + +Tickets: https://tickets.puppet.com/browse/MODULES. Make sure to set component to `stdlib`. diff --git a/environments/production/modules/stdlib/NOTICE b/environments/production/modules/stdlib/NOTICE new file mode 100644 index 0000000..3c8c03a --- /dev/null +++ b/environments/production/modules/stdlib/NOTICE @@ -0,0 +1,23 @@ +stdlib puppet module + +Copyright (C) 2011-2016 Puppet Labs, Inc. + +and some parts: + +Copyright (C) 2011 Krzysztof Wilczynski + + +Puppet Labs can be contacted at: info@puppetlabs.com + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/environments/production/modules/stdlib/README.markdown b/environments/production/modules/stdlib/README.markdown new file mode 100644 index 0000000..03daae9 --- /dev/null +++ b/environments/production/modules/stdlib/README.markdown @@ -0,0 +1,1791 @@ +# stdlib + +#### Table of Contents + +1. [Overview](#overview) +2. [Module Description - What the module does and why it is useful](#module-description) +3. [Setup - The basics of getting started with stdlib](#setup) +4. [Usage - Configuration options and additional functionality](#usage) +5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +5. [Limitations - OS compatibility, etc.](#limitations) +6. [Development - Guide for contributing to the module](#development) + +## Overview + +Adds a standard library of resources for Puppet modules. + +## Module Description + +This module provides a standard library of resources for the development of Puppet modules. Puppet modules make heavy use of this standard library. The stdlib module adds the following resources to Puppet: + + * Stages + * Facts + * Functions + * Defined resource types + * Data Types + * Providers + +> *Note:* As of version 3.7, Puppet Enterprise no longer includes the stdlib module. If you're running Puppet Enterprise, you should install the most recent release of stdlib for compatibility with Puppet modules. + +## Setup + +Installing the stdlib module adds the functions, facts, and resources of this standard library to Puppet. + +## Usage + +After you've installed stdlib, all of its functions, facts, and resources are available for module use or development. + +If you want to use a standardized set of run stages for Puppet, `include stdlib` in your manifest. + +* `stdlib`: Most of stdlib's features are automatically loaded by Puppet. To use standardized run stages in Puppet, declare this class in your manifest with `include stdlib`. + + When declared, stdlib declares all other classes in the module. The only other class currently included in the module is `stdlib::stages`. + +The `stdlib::stages` class declares various run stages for deploying infrastructure, language runtimes, and application layers. The high level stages are (in order): + + * setup + * main + * runtime + * setup_infra + * deploy_infra + * setup_app + * deploy_app + * deploy + + Sample usage: + + ~~~ + node default { + include stdlib + class { java: stage => 'runtime' } + } + ~~~ + +## Reference + +### Classes + +#### Public Classes + + The stdlib class has no parameters. + +#### Private Classes + +* `stdlib::stages`: Manages a standard set of run stages for Puppet. It is managed by the stdlib class and should not be declared independently. + +### Resource Types + +#### `file_line` + +Ensures that a given line is contained within a file. The implementation matches the full line, including whitespace at the beginning and end. If the line is not contained in the given file, Puppet appends the line to the end of the file to ensure the desired state. Multiple resources can be declared to manage multiple lines in the same file. + +Example: + + file_line { 'sudo_rule': + path => '/etc/sudoers', + line => '%sudo ALL=(ALL) ALL', + } + + file_line { 'sudo_rule_nopw': + path => '/etc/sudoers', + line => '%sudonopw ALL=(ALL) NOPASSWD: ALL', + } + +In this example, Puppet ensures that both of the specified lines are contained in the file `/etc/sudoers`. + +Match Example: + + file_line { 'bashrc_proxy': + ensure => present, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + } + +In this code example, `match` looks for a line beginning with export followed by HTTP_PROXY and replaces it with the value in line. + +Match Example With `ensure => absent`: + + file_line { 'bashrc_proxy': + ensure => absent, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + match_for_absence => true, + } + +In this code example, `match` looks for a line beginning with export +followed by HTTP_PROXY and delete it. If multiple lines match, an +error will be raised unless the `multiple => true` parameter is set. + +Encoding example: + + file_line { "XScreenSaver": + ensure => present, + path => '/root/XScreenSaver' + line => "*lock: 10:00:00", + match => '^*lock:', + encoding => "iso-8859-1", + } + +Files with special characters that are not valid UTF-8 will give the +error message "invalid byte sequence in UTF-8". In this case, determine +the correct file encoding and specify the correct encoding using the +encoding attribute, the value of which needs to be a valid Ruby character +encoding. + +**Autorequires:** If Puppet is managing the file that contains the line being managed, the `file_line` resource autorequires that file. + +##### Parameters + +All parameters are optional, unless otherwise noted. + +* `after`: Specifies the line after which Puppet adds any new lines using a regular expression. (Existing lines are added in place.) Valid options: String containing a regex. Default: Undefined. +* `ensure`: Ensures whether the resource is present. Valid options: 'present', 'absent'. Default: 'present'. +* `line`: **Required.** Sets the line to be added to the file located by the `path` parameter. Valid options: String. Default: Undefined. +* `match`: Specifies a regular expression to run against existing lines in the file; if a match is found, it is replaced rather than adding a new line. A regex comparison is performed against the line value, and if it does not match, an exception is raised. Valid options: String containing a regex. Default: Undefined. +* `match_for_absence`: An optional value to determine if match should be applied when `ensure => absent`. If set to true and match is set, the line that matches match will be deleted. If set to false (the default), match is ignored when `ensure => absent` and the value of `line` is used instead. Ignored when `ensure => present`. Default: false. +* `multiple`: Determines if `match` and/or `after` can change multiple lines. If set to false, an exception will be raised if more than one line matches. Valid options: 'true', 'false'. Default: Undefined. +* `name`: Sets the name to use as the identity of the resource. This is necessary if you want the resource namevar to differ from the supplied `title` of the resource. Valid options: String. Default: Undefined. +* `path`: **Required.** Defines the file in which Puppet will ensure the line specified by `line`. Must be an absolute path to the file. +* `replace`: Defines whether the resource will overwrite an existing line that matches the `match` parameter. If set to false and a line is found matching the `match` param, the line will not be placed in the file. Valid options: true, false, yes, no. Default: true + +### Data Types + +#### `Stdlib::Absolutepath` + +A strict absolute path type. Uses a Variant of Unixpath and Windowspath types. + +Acceptable input examples: /var/log + /usr2/username/bin:/usr/local/bin:/usr/bin:. + C:\\WINDOWS\\System32 +Unacceptable input example: ../relative_path + +#### `Stdlib::Httpsurl` + +Matches https URLs. + +Acceptable input example: https://hello.com +Unacceptable input example: httds://notquiteright.org + +#### `Stdlib::Httpurl` + +Matches both https and http URLs. + +Acceptable input example: https://hello.com + http://hello.com +Unacceptable input example: httds://notquiteright.org + +#### `Stdlib::Unixpath` + +Matches paths on Unix type Operating Systems. + +Acceptable input example: /usr2/username/bin:/usr/local/bin:/usr/bin:. + /var/tmp +Unacceptable input example: C:/whatever + +#### `Stdlib::Windowspath` + +Matches paths on Windows Operating systems. + +Acceptable input example: C:\\WINDOWS\\System32 + C:\\ + \\\\host\\windows +Unacceptable input example: /usr2/username/bin:/usr/local/bin:/usr/bin:. + +### Functions + +#### `abs` + +Returns the absolute value of a number; for example, '-34.56' becomes '34.56'. Takes a single integer and float value as an argument. *Type*: rvalue. + +#### `any2array` + +Converts any object to an array containing that object. Empty argument lists are converted to an empty array. Arrays are left untouched. Hashes are converted to arrays of alternating keys and values. *Type*: rvalue. + +#### `base64` + +Converts a string to and from base64 encoding. Requires an `action` ('encode', 'decode') and either a plain or base64-encoded `string`, and an optional `method` ('default', 'strict', 'urlsafe') + +For backward compatibility, `method` will be set as `default` if not specified. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +*Examples:* +~~~ +base64('encode', 'hello') +base64('encode', 'hello', 'default') +# return: "aGVsbG8=\n" + +base64('encode', 'hello', 'strict') +# return: "aGVsbG8=" + +base64('decode', 'aGVsbG8=') +base64('decode', 'aGVsbG8=\n') +base64('decode', 'aGVsbG8=', 'default') +base64('decode', 'aGVsbG8=\n', 'default') +base64('decode', 'aGVsbG8=', 'strict') +# return: "hello" + +base64('encode', 'https://puppetlabs.com', 'urlsafe') +# return: "aHR0cHM6Ly9wdXBwZXRsYWJzLmNvbQ==" + +base64('decode', 'aHR0cHM6Ly9wdXBwZXRsYWJzLmNvbQ==', 'urlsafe') +# return: "https://puppetlabs.com" +~~~ + +*Type*: rvalue. + +#### `basename` + +Returns the `basename` of a path (optionally stripping an extension). For example: + * ('/path/to/a/file.ext') returns 'file.ext' + * ('relative/path/file.ext') returns 'file.ext' + * ('/path/to/a/file.ext', '.ext') returns 'file' + +*Type*: rvalue. + +#### `bool2num` + +Converts a boolean to a number. Converts values: + * 'false', 'f', '0', 'n', and 'no' to 0. + * 'true', 't', '1', 'y', and 'yes' to 1. + Requires a single boolean or string as an input. *Type*: rvalue. + +#### `bool2str` + +Converts a boolean to a string using optionally supplied arguments. The optional second and third arguments represent what true and false are converted to respectively. If only one argument is given, it is converted from a boolean to a string containing 'true' or 'false'. + +*Examples:* +~~~ +bool2str(true) => 'true' +bool2str(true, 'yes', 'no') => 'yes' +bool2str(false, 't', 'f') => 'f' +~~~ + +Requires a single boolean as input. *Type*: rvalue. + +#### `camelcase` + +Converts the case of a string or all strings in an array to camel case. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `capitalize` + +Capitalizes the first character of a string or array of strings and lowercases the remaining characters of each string. Requires either a single string or an array as an input. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `ceiling` + +Returns the smallest integer greater than or equal to the argument. Takes a single numeric value as an argument. *Type*: rvalue. + +#### `chomp` + +Removes the record separator from the end of a string or an array of strings; for example, 'hello\n' becomes 'hello'. Requires a single string or array as an input. *Type*: rvalue. + +#### `chop` + +Returns a new string with the last character removed. If the string ends with '\r\n', both characters are removed. Applying `chop` to an empty string returns an empty string. If you want to merely remove record separators, then you should use the `chomp` function. Requires a string or an array of strings as input. *Type*: rvalue. + +#### `clamp` + +Keeps value within the range [Min, X, Max] by sort based on integer value (order of params doesn't matter). Takes strings, arrays or numerics. Strings are converted and compared numerically. Arrays of values are flattened into a list for further handling. For example: + * `clamp('24', [575, 187])` returns 187. + * `clamp(16, 88, 661)` returns 88. + * `clamp([4, 3, '99'])` returns 4. + *Type*: rvalue. + +#### `concat` + +Appends the contents of multiple arrays onto the first array given. For example: + * `concat(['1','2','3'],'4')` returns ['1','2','3','4']. + * `concat(['1','2','3'],'4',['5','6','7'])` returns ['1','2','3','4','5','6','7']. + *Type*: rvalue. + +#### `convert_base` + +Converts a given integer or base 10 string representing an integer to a specified base, as a string. For example: + * `convert_base(5, 2)` results in: '101' + * `convert_base('254', '16')` results in: 'fe' + +#### `count` + +If called with only an array, it counts the number of elements that are **not** nil/undef. If called with a second argument, counts the number of elements in an array that matches the second argument. *Type*: rvalue. + +#### `deep_merge` + +Recursively merges two or more hashes together and returns the resulting hash. + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + +The resulting hash is equivalent to: + $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + +When there is a duplicate key that is a hash, they are recursively merged. When there is a duplicate key that is not a hash, the key in the rightmost hash will "win.". +*Type*: rvalue, rvalue. + +#### `defined_with_params` + +Takes a resource reference and an optional hash of attributes. Returns 'true' if a resource with the specified attributes has already been added to the catalog. Returns 'false' otherwise. + + ~~~ + user { 'dan': + ensure => present, + } + + if ! defined_with_params(User[dan], {'ensure' => 'present' }) { + user { 'dan': ensure => present, } + } + ~~~ + +*Type*: rvalue. + +#### `delete` + +Deletes all instances of a given element from an array, substring from a string, or key from a hash. + +For example, `delete(['a','b','c','b'], 'b')` returns ['a','c']; `delete('abracadabra', 'bra')` returns 'acada'. `delete({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete(['ab', 'b'], 'b')` returns ['ab']. + +*Type*: rvalue. + +#### `delete_at` + +Deletes a determined indexed value from an array. For example, `delete_at(['a','b','c'], 1)` returns ['a','c']. *Type*: rvalue. + +#### `delete_regex` + +Deletes all instances of a given element from an array or hash that match a provided regular expression. A string will be treated as a one-item array. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +For example, `delete_regex(['a','b','c','b'], 'b')` returns ['a','c']; `delete_regex({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete_regex(['abf', 'ab', 'ac'], '^ab.*')` returns ['ac']. `delete_regex(['ab', 'b'], 'b')` returns ['ab']. + +*Type*: rvalue. + +#### `delete_values` + +Deletes all instances of a given value from a hash. For example, `delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B')` returns {'a'=>'A','c'=>'C','B'=>'D'} *Type*: rvalue. + +#### `delete_undef_values` + +Deletes all instances of the undef value from an array or hash. For example, `$hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})` returns {a => 'A', b => '', d => false}. *Type*: rvalue. + +#### `deprecation` + +Prints deprecation warnings and logs a warning once for a given key: + +``` +deprecation(key, message) +``` + +* key: to keep the number of messages low, during the lifetime of a puppet process, only one message per key is logged. +* message: the text to be logged. + +The Puppet settings '[disable_warnings](https://docs.puppet.com/puppet/latest/reference/configuration.html#disablewarnings)', '[max_deprecations](https://docs.puppet.com/puppet/latest/reference/configuration.html#maxdeprecations)', and '[strict](https://docs.puppet.com/puppet/latest/reference/configuration.html#strict)' affect this function. Set 'strict' to `error` to fail immediately with the deprecation message, `off` to output emit no messages at all, or `warning` (default) to log all warnings. + +Additionally you can set the environment variable `STDLIB_LOG_DEPRECATIONS` to decide whether or not to log deprecation warnings: if this environment variable is set to `true`, the functions log a warning, if it is set to `false`, no warnings are logged. If no value is set at all, Puppet 4 will emit warnings, while Puppet 3 will not. Using this setting is especially useful for automated tests to avoid flooding your logs before you are ready to migrate. + +*Type*: String, String. + +#### `difference` + +Returns the difference between two arrays. The returned array is a copy of the original array, removing any items that also appear in the second array. For example, `difference(["a","b","c"],["b","c","d"])` returns ["a"]. *Type*: rvalue. + +#### `dig` + +DEPRECATED: This function has been replaced in Puppet 4.5.0, use dig44() for backwards compatibility or use the new version. + +*Type*: rvalue. + +Retrieves a value within multiple layers of hashes and arrays via an array of keys containing a path. The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig($data, ['a', 'b', 2]) +# $value = 'b3' + +# with all possible options +$value = dig($data, ['a', 'b', 2], 'not_found') +# $value = 'b3' + +# using the default value +$value = dig($data, ['a', 'b', 'c', 'd'], 'not_found') +# $value = 'not_found' +~~~ + +1. **$data** The data structure we are working with. +2. **['a', 'b', 2]** The path array. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) + +#### `dig44` + +*Type*: rvalue. + +Retrieves a value within multiple layers of hashes and arrays via an array of keys containing a path. The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig44($data, ['a', 'b', 2]) +# $value = 'b3' + +# with all possible options +$value = dig44($data, ['a', 'b', 2], 'not_found') +# $value = 'b3' + +# using the default value +$value = dig44($data, ['a', 'b', 'c', 'd'], 'not_found') +# $value = 'not_found' +~~~ + +1. **$data** The data structure we are working with. +2. **['a', 'b', 2]** The path array. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) + +#### `dirname` + +Returns the `dirname` of a path. For example, `dirname('/path/to/a/file.ext')` returns '/path/to/a'. *Type*: rvalue. + +#### `dos2unix` + +Returns the Unix version of the given string. Very useful when using a File resource with a cross-platform template. *Type*: rvalue. + +~~~ +file{$config_file: + ensure => file, + content => dos2unix(template('my_module/settings.conf.erb')), +} +~~~ + +See also [unix2dos](#unix2dos). + +#### `downcase` + +Converts the case of a string or of all strings in an array to lowercase. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `empty` + +Returns true if the argument is an array or hash that contains no elements, or an empty string. Returns false when the argument is a numerical value. *Type*: rvalue. + +#### `enclose_ipv6` + +Takes an array of ip addresses and encloses the ipv6 addresses with square brackets. *Type*: rvalue. + +#### `ensure_packages` + +Takes a list of packages array/hash and only installs them if they don't already exist. It optionally takes a hash as a second parameter to be passed as the third argument to the `ensure_resource()` or `ensure_resources()` function. *Type*: statement. + +For Array: + + ensure_packages(['ksh','openssl'], {'ensure' => 'present'}) + +For Hash: + + ensure_packages({'ksh' => { ensure => '20120801-1' } , 'mypackage' => { source => '/tmp/myrpm-1.0.0.x86_64.rpm', provider => "rpm" }}, {'ensure' => 'present'}) + +#### `ensure_resource` + +Takes a resource type, title, and a hash of attributes that describe the resource(s). + +~~~ +user { 'dan': + ensure => present, +} +~~~ + +This example only creates the resource if it does not already exist: + + `ensure_resource('user', 'dan', {'ensure' => 'present' })` + +If the resource already exists, but does not match the specified parameters, this function attempts to recreate the resource, leading to a duplicate resource definition error. + +An array of resources can also be passed in, and each will be created with the type and parameters specified if it doesn't already exist. + + `ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})` + +*Type*: statement. + +#### `ensure_resources` + +Takes a resource type, title (only hash), and a hash of attributes that describe the resource(s). + +~~~ +user { 'dan': + gid => 'mygroup', + ensure => present, +} + +ensure_resources($user) +~~~ + +An hash of resources should be passed in and each will be created with the type and parameters specified if it doesn't already exist: + + ensure_resources('user', {'dan' => { gid => 'mygroup', uid => '600' } , 'alex' => { gid => 'mygroup' }}, {'ensure' => 'present'}) + +From Hiera Backend: + +~~~ +userlist: + dan: + gid: 'mygroup' + uid: '600' + alex: + gid: 'mygroup' + +ensure_resources('user', hiera_hash('userlist'), {'ensure' => 'present'}) +~~~ + +### `flatten` + +Flattens deeply nested arrays and returns a single flat array as a result. For example, `flatten(['a', ['b', ['c']]])` returns ['a','b','c']. *Type*: rvalue. + +#### `floor` + +Takes a single numeric value as an argument, and returns the largest integer less than or equal to the argument. *Type*: rvalue. + +#### `fqdn_rand_string` + +Generates a random alphanumeric string using an optionally-specified character set (default is alphanumeric), combining the `$fqdn` fact and an optional seed for repeatable randomness. + +*Usage:* +~~~ +fqdn_rand_string(LENGTH, [CHARSET], [SEED]) +~~~ +*Examples:* +~~~ +fqdn_rand_string(10) +fqdn_rand_string(10, 'ABCDEF!@#$%^') +fqdn_rand_string(10, '', 'custom seed') +~~~ + +*Type*: rvalue. + +#### `fqdn_rotate` + +Rotates an array or string a random number of times, combining the `$fqdn` fact and an optional seed for repeatable randomness. + +*Usage:* + +~~~ +fqdn_rotate(VALUE, [SEED]) +~~~ + +*Examples:* + +~~~ +fqdn_rotate(['a', 'b', 'c', 'd']) +fqdn_rotate('abcd') +fqdn_rotate([1, 2, 3], 'custom seed') +~~~ + +*Type*: rvalue. + +#### `fqdn_uuid` + +Returns a rfc4122 valid version 5 UUID based on an FQDN string under the DNS namespace + + * fqdn_uuid('puppetlabs.com') returns '9c70320f-6815-5fc5-ab0f-debe68bf764c' + * fqdn_uuid('google.com') returns '64ee70a4-8cc1-5d25-abf2-dea6c79a09c8' + +*Type*: rvalue. + +#### `get_module_path` + +Returns the absolute path of the specified module for the current environment. + + `$module_path = get_module_path('stdlib')` + +*Type*: rvalue. + +#### `getparam` + +Takes a resource reference and the name of the parameter, and returns the value of the resource's parameter. + +For example, the following returns 'param_value': + + ~~~ + define example_resource($param) { + } + + example_resource { "example_resource_instance": + param => "param_value" + } + + getparam(Example_resource["example_resource_instance"], "param") + ~~~ + +*Type*: rvalue. + +#### `getvar` + +Looks up a variable in a remote namespace. + +For example: + + ~~~ + $foo = getvar('site::data::foo') + # Equivalent to $foo = $site::data::foo + ~~~ + +This is useful if the namespace itself is stored in a string: + + ~~~ + $datalocation = 'site::data' + $bar = getvar("${datalocation}::bar") + # Equivalent to $bar = $site::data::bar + ~~~ + +*Type*: rvalue. + +#### `grep` + +Searches through an array and returns any elements that match the provided regular expression. For example, `grep(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['aaa','aaaddd']. *Type*: rvalue. + +#### `has_interface_with` + +Returns a boolean based on kind and value: + * macaddress + * netmask + * ipaddress + * network + +*Examples:* + + ~~~ + has_interface_with("macaddress", "x:x:x:x:x:x") + has_interface_with("ipaddress", "127.0.0.1") => true + ~~~ + +If no kind is given, then the presence of the interface is checked: + + ~~~ + has_interface_with("lo") => true + ~~~ + +*Type*: rvalue. + +#### `has_ip_address` + +Returns 'true' if the client has the requested IP address on some interface. This function iterates through the `interfaces` fact and checks the `ipaddress_IFACE` facts, performing a simple string comparison. *Type*: rvalue. + +#### `has_ip_network` + +Returns 'true' if the client has an IP address within the requested network. This function iterates through the `interfaces` fact and checks the `network_IFACE` facts, performing a simple string comparision. *Type*: rvalue. + +#### `has_key` + +Determines if a hash has a certain key value. + +*Example*: + + ~~~ + $my_hash = {'key_one' => 'value_one'} + if has_key($my_hash, 'key_two') { + notice('we will not reach here') + } + if has_key($my_hash, 'key_one') { + notice('this will be printed') + } + ~~~ + +*Type*: rvalue. + +#### `hash` + +Converts an array into a hash. For example, `hash(['a',1,'b',2,'c',3])` returns {'a'=>1,'b'=>2,'c'=>3}. *Type*: rvalue. + +#### `intersection` + +Returns an array an intersection of two. For example, `intersection(["a","b","c"],["b","c","d"])` returns ["b","c"]. *Type*: rvalue. + +#### `is_a` + +Boolean check to determine whether a variable is of a given data type. This is equivalent to the `=~` type checks. This function is available only in Puppet 4 or in Puppet 3 with the "future" parser. + + ~~~ + foo = 3 + $bar = [1,2,3] + $baz = 'A string!' + + if $foo.is_a(Integer) { + notify { 'foo!': } + } + if $bar.is_a(Array) { + notify { 'bar!': } + } + if $baz.is_a(String) { + notify { 'baz!': } + } + ~~~ + +See the [the Puppet type system](https://docs.puppetlabs.com/references/latest/type.html#about-resource-types) for more information about types. +See the [`assert_type()`](https://docs.puppetlabs.com/references/latest/function.html#asserttype) function for flexible ways to assert the type of a value. + +#### `is_absolute_path` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the given path is absolute. *Type*: rvalue. + +#### `is_array` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is an array. *Type*: rvalue. + +#### `is_bool` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a boolean. *Type*: rvalue. + +#### `is_domain_name` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a syntactically correct domain name. *Type*: rvalue. + +#### `is_float` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a float. *Type*: rvalue. + +#### `is_function_available` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Accepts a string as an argument and determines whether the Puppet runtime has access to a function by that name. It returns 'true' if the function exists, 'false' if not. *Type*: rvalue. + +#### `is_hash` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a hash. *Type*: rvalue. + +#### `is_integer` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable returned to this string is an integer. *Type*: rvalue. + +#### `is_ip_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IP address. *Type*: rvalue. + +#### `is_ipv6_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IPv6 address. *Type*: rvalue. + +#### `is_ipv4_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the string passed to this function is a valid IPv4 address. *Type*: rvalue. + +#### `is_mac_address` + +Returns 'true' if the string passed to this function is a valid MAC address. *Type*: rvalue. + +#### `is_numeric` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a number. *Type*: rvalue. + +#### `is_string` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Returns 'true' if the variable passed to this function is a string. *Type*: rvalue. + +#### `join` + +Joins an array into a string using a separator. For example, `join(['a','b','c'], ",")` results in: "a,b,c". *Type*: rvalue. + +#### `join_keys_to_values` + +Joins each key of a hash to that key's corresponding value with a separator. Keys are cast to strings. +If values are arrays, multiple keys are added for each element. +The return value is an array in which each element is one joined key/value pair. For example, `join_keys_to_values({'a'=>1,'b'=>[2,3]}, " is ")` results in ["a is 1","b is 2","b is 3"]. *Type*: rvalue. + +#### `keys` + +Returns the keys of a hash as an array. *Type*: rvalue. + +#### `length` + +Returns the length of a given string, array or hash. To eventually replace the deprecated size() function as can handle the new type functionality introduced in Puppet 4. *Type*: rvalue. + +#### `loadyaml` + +Loads a YAML file containing an array, string, or hash, and returns the data in the corresponding native data type. + +For example: + + ~~~ + $myhash = loadyaml('/etc/puppet/data/myhash.yaml') + ~~~ + +The second parameter will be returned if the file was not found or could not be parsed. + +For example: + + ~~~ + $myhash = loadyaml('no-file.yaml', {'default'=>'value'}) + ~~~ + +*Type*: rvalue. + +#### `loadjson` + +Loads a JSON file containing an array, string, or hash, and returns the data in the corresponding native data type. + +For example: + + ~~~ + $myhash = loadjson('/etc/puppet/data/myhash.json') + ~~~ + +The second parameter will be returned if the file was not found or could not be parsed. + +For example: + + ~~~ + $myhash = loadjson('no-file.json', {'default'=>'value'}) + ~~~ + +*Type*: rvalue. + +#### `load_module_metadata` + +Loads the metadata.json of a target module. Can be used to determine module version and authorship for dynamic support of modules. + + ~~~ + $metadata = load_module_metadata('archive') + notify { $metadata['author']: } + ~~~ + +If you do not want to fail the catalog compilation when a module's metadata file is absent: + + ~~~ + $metadata = load_module_metadata('mysql', true) + if empty($metadata) { + notify { "This module does not have a metadata.json file.": } + } + ~~~ + +*Type*: rvalue. + +#### `lstrip` + +Strips spaces to the left of a string. *Type*: rvalue. + +#### `max` + +Returns the highest value of all arguments. Requires at least one argument. *Type*: rvalue. + +#### `member` + +This function determines if a variable is a member of an array. The variable can be either a string, array, or fixnum. For example, `member(['a','b'], 'b')` and `member(['a','b','c'], ['b','c'])` return 'true', while `member(['a','b'], 'c')` and `member(['a','b','c'], ['c','d'])` return 'false'. *Note*: This function does not support nested arrays. If the first argument contains nested arrays, it will not recurse through them. + +*Type*: rvalue. + +#### `merge` + +Merges two or more hashes together and returns the resulting hash. + +*Example*: + + ~~~ + $hash1 = {'one' => 1, 'two' => 2} + $hash2 = {'two' => 'dos', 'three' => 'tres'} + $merged_hash = merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'} + ~~~ + +When there is a duplicate key, the key in the rightmost hash "wins." *Type*: rvalue. + +#### `min` + +Returns the lowest value of all arguments. Requires at least one argument. *Type*: rvalue. + +#### `num2bool` + +Converts a number or a string representation of a number into a true boolean. Zero or anything non-numeric becomes 'false'. Numbers greater than 0 become 'true'. *Type*: rvalue. + +#### `parsejson` + +Converts a string of JSON into the correct Puppet structure. *Type*: rvalue. The optional second argument is returned if the data was not correct. + +#### `parseyaml` + +Converts a string of YAML into the correct Puppet structure. *Type*: rvalue. The optional second argument is returned if the data was not correct. + +#### `pick` + +From a list of values, returns the first value that is not undefined or an empty string. Takes any number of arguments, and raises an error if all values are undefined or empty. + + ~~~ + $real_jenkins_version = pick($::jenkins_version, '1.449') + ~~~ + +*Type*: rvalue. + +#### `pick_default` + +Returns the first value in a list of values. Contrary to the `pick()` function, the `pick_default()` does not fail if all arguments are empty. This allows it to use an empty value as default. *Type*: rvalue. + +#### `prefix` + +Applies a prefix to all elements in an array, or to the keys in a hash. +For example: +* `prefix(['a','b','c'], 'p')` returns ['pa','pb','pc'] +* `prefix({'a'=>'b','b'=>'c','c'=>'d'}, 'p')` returns {'pa'=>'b','pb'=>'c','pc'=>'d'}. + +*Type*: rvalue. + +#### `pry` + +This function invokes a pry debugging session in the current scope object. This is useful for debugging manifest code at specific points during a compilation. Should only be used when running `puppet apply` or running a puppet master in the foreground. This requires the `pry` gem to be installed in puppet's rubygems. + +*Examples:* +```puppet +pry() +``` +Once in a pry session, some interesting commands: + +* Run `catalog` to see the contents currently compiling catalog +* Run `cd catalog` and `ls` to see catalog methods and instance variables +* Run `@resource_table` to see the current catalog resource table + +#### `assert_private` + +Sets the current class or definition as private. Calling the class or definition from outside the current module will fail. + +For example, `assert_private()` called in class `foo::bar` outputs the following message if class is called from outside module `foo`: + + ~~~ + Class foo::bar is private + ~~~ + + To specify the error message you want to use: + + ~~~ + assert_private("You're not supposed to do that!") + ~~~ + +*Type*: statement. + +#### `pw_hash` + +Hashes a password using the crypt function. Provides a hash usable on most POSIX systems. + +The first argument to this function is the password to hash. If it is undef or an empty string, this function returns undef. + +The second argument to this function is which type of hash to use. It will be converted into the appropriate crypt(3) hash specifier. Valid hash types are: + +|Hash type |Specifier| +|---------------------|---------| +|MD5 |1 | +|SHA-256 |5 | +|SHA-512 (recommended)|6 | + +The third argument to this function is the salt to use. + +*Type*: rvalue. + +**Please note:** This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +**Note:** this uses the Puppet master's implementation of crypt(3). If your environment contains several different operating systems, ensure that they are compatible before using this function. + +#### `range` + +Extrapolates a range as an array when given in the form of '(start, stop)'. For example, `range("0", "9")` returns [0,1,2,3,4,5,6,7,8,9]. Zero-padded strings are converted to integers automatically, so `range("00", "09")` returns [0,1,2,3,4,5,6,7,8,9]. + +Non-integer strings are accepted; `range("a", "c")` returns ["a","b","c"], and `range("host01", "host10")` returns ["host01", "host02", ..., "host09", "host10"]. +NB Be explicit in including trailing zeros. Otherwise the underlying ruby function will fail. + +Passing a third argument will cause the generated range to step by that interval, e.g. `range("0", "9", "2")` returns ["0","2","4","6","8"]. + +*Type*: rvalue. + +#### `regexpescape` + +Regexp escape a string or array of strings. Requires either a single string or an array as an input. *Type*: rvalue. + +#### `reject` + +Searches through an array and rejects all elements that match the provided regular expression. For example, `reject(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['bbb','ccc']. *Type*: rvalue. + +#### `reverse` + +Reverses the order of a string or array. *Type*: rvalue. + +#### `rstrip` + +Strips spaces to the right of the string. *Type*: rvalue. + +#### `seeded_rand` + +Takes an integer max value and a string seed value and returns a repeatable random integer smaller than max. Like `fqdn_rand`, but does not add node specific data to the seed. *Type*: rvalue. + +#### `shell_escape` + +Escapes a string so that it can be safely used in a Bourne shell command line. Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single quotes. This function behaves the same as ruby's `Shellwords.shellescape()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellescape). + +*Example:* +~~~ +shell_escape('foo b"ar') => 'foo\ b\"ar' +~~~ + +*Type*: rvalue. + +#### `shell_join` + +Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are +then joined together, with a single space in between. This function behaves the same as ruby's `Shellwords.shelljoin()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shelljoin). + +*Example:* +~~~ +shell_join(['foo bar', 'ba"z']) => 'foo\ bar ba\"z' +~~~ + +*Type*: rvalue. + +#### `shell_split` + +Splits a string into an array of tokens in the same way the Bourne shell does. This function behaves the same as ruby's `Shellwords.shellsplit()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellsplit). + +*Example:* +~~~ +shell_split('foo\ bar ba\"z') => ['foo bar', 'ba"z'] +~~~ + +*Type*: rvalue. + +#### `shuffle` + +Randomizes the order of a string or array elements. *Type*: rvalue. + +#### `size` + +Returns the number of elements in a string, an array or a hash. May get confused around Puppet 4 type values and as such is to be deprecated in the next release and replaced with the new stdlib length function. *Type*: rvalue. + +#### `sort` + +Sorts strings and arrays lexically. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `squeeze` + +Returns a new string where runs of the same character that occur in this set are replaced by a single character. *Type*: rvalue. + +#### `str2bool` + +Converts certain strings to a boolean. This attempts to convert strings that contain the values '1', 'true', 't', 'y', or 'yes' to true. Strings that contain values '0', 'false', 'f', 'n', or 'no', or that are an empty string or undefined are converted to false. Any other value causes an error. These checks are case insensitive. *Type*: rvalue. + +#### `str2saltedsha512` + +Converts a string to a salted-SHA512 password hash, used for OS X versions >= 10.7. Given any string, this function returns a hex version of a salted-SHA512 password hash, which can be inserted into your Puppet +manifests as a valid password attribute. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `strftime` + +Returns formatted time. For example, `strftime("%s")` returns the time since Unix epoch, and `strftime("%Y-%m-%d")` returns the date. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + + *Format:* + + * `%a`: The abbreviated weekday name ('Sun') + * `%A`: The full weekday name ('Sunday') + * `%b`: The abbreviated month name ('Jan') + * `%B`: The full month name ('January') + * `%c`: The preferred local date and time representation + * `%C`: Century (20 in 2009) + * `%d`: Day of the month (01..31) + * `%D`: Date (%m/%d/%y) + * `%e`: Day of the month, blank-padded ( 1..31) + * `%F`: Equivalent to %Y-%m-%d (the ISO 8601 date format) + * `%h`: Equivalent to %b + * `%H`: Hour of the day, 24-hour clock (00..23) + * `%I`: Hour of the day, 12-hour clock (01..12) + * `%j`: Day of the year (001..366) + * `%k`: Hour, 24-hour clock, blank-padded ( 0..23) + * `%l`: Hour, 12-hour clock, blank-padded ( 0..12) + * `%L`: Millisecond of the second (000..999) + * `%m`: Month of the year (01..12) + * `%M`: Minute of the hour (00..59) + * `%n`: Newline (\n) + * `%N`: Fractional seconds digits, default is 9 digits (nanosecond) + * `%3N`: Millisecond (3 digits) + * `%6N`: Microsecond (6 digits) + * `%9N`: Nanosecond (9 digits) + * `%p`: Meridian indicator ('AM' or 'PM') + * `%P`: Meridian indicator ('am' or 'pm') + * `%r`: Time, 12-hour (same as %I:%M:%S %p) + * `%R`: Time, 24-hour (%H:%M) + * `%s`: Number of seconds since the Unix epoch, 1970-01-01 00:00:00 UTC. + * `%S`: Second of the minute (00..60) + * `%t`: Tab character ( ) + * `%T`: Time, 24-hour (%H:%M:%S) + * `%u`: Day of the week as a decimal, Monday being 1. (1..7) + * `%U`: Week number of the current year, starting with the first Sunday as the first day of the first week (00..53) + * `%v`: VMS date (%e-%b-%Y) + * `%V`: Week number of year according to ISO 8601 (01..53) + * `%W`: Week number of the current year, starting with the first Monday as the first day of the first week (00..53) + * `%w`: Day of the week (Sunday is 0, 0..6) + * `%x`: Preferred representation for the date alone, no time + * `%X`: Preferred representation for the time alone, no date + * `%y`: Year without a century (00..99) + * `%Y`: Year with century + * `%z`: Time zone as hour offset from UTC (e.g. +0900) + * `%Z`: Time zone name + * `%%`: Literal '%' character + +#### `strip` + +Removes leading and trailing whitespace from a string or from every string inside an array. For example, `strip(" aaa ")` results in "aaa". *Type*: rvalue. + +#### `suffix` + +Applies a suffix to all elements in an array, or to the keys in a hash. +For example: +* `suffix(['a','b','c'], 'p')` returns ['ap','bp','cp'] +* `suffix({'a'=>'b','b'=>'c','c'=>'d'}, 'p')` returns {'ap'=>'b','bp'=>'c','cp'=>'d'}. + +*Type*: rvalue. + +#### `swapcase` + +Swaps the existing case of a string. For example, `swapcase("aBcD")` results in "AbCd". *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `time` + +Returns the current Unix epoch time as an integer. For example, `time()` returns something like '1311972653'. *Type*: rvalue. + +#### `to_bytes` + +Converts the argument into bytes, for example "4 kB" becomes "4096". Takes a single string value as an argument. *Type*: rvalue. + +#### `try_get_value` + +*Type*: rvalue. + +DEPRECATED: replaced by `dig()`. + +Retrieves a value within multiple layers of hashes and arrays via a string containing a path. The path is a string of hash keys or array indexes starting with zero, separated by the path separator character (default "/"). The function goes through the structure by each path component and tries to return the value at the end of the path. + +In addition to the required path argument, the function accepts the default argument. It is returned if the path is not correct, if no value was found, or if any other error has occurred. The last argument can set the path separator character. + +~~~ruby +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = try_get_value($data, 'a/b/2') +# $value = 'b3' + +# with all possible options +$value = try_get_value($data, 'a/b/2', 'not_found', '/') +# $value = 'b3' + +# using the default value +$value = try_get_value($data, 'a/b/c/d', 'not_found') +# $value = 'not_found' + +# using custom separator +$value = try_get_value($data, 'a|b', [], '|') +# $value = ['b1','b2','b3'] +~~~ + +1. **$data** The data structure we are working with. +2. **'a/b/2'** The path string. +3. **'not_found'** The default value. It will be returned if nothing is found. + (optional, defaults to *undef*) +4. **'/'** The path separator character. + (optional, defaults to *'/'*) + +#### `type3x` + +Returns a string description of the type when passed a value. Type can be a string, array, hash, float, integer, or boolean. This function will be removed when Puppet 3 support is dropped and the new type system can be used. *Type*: rvalue. + +#### `type_of` + +This function is provided for backwards compatibility but is generally not preferred over the built-in [type() function](https://docs.puppet.com/puppet/latest/reference/function.html#type) provided by Puppet. + +Returns the literal type when passed a value. Requires the new parser. Useful for comparison of types with `<=` such as in `if type_of($some_value) <= Array[String] { ... }` (which is equivalent to `if $some_value =~ Array[String] { ... }`) *Type*: rvalue. + +#### `union` + +Returns a union of two or more arrays, without duplicates. For example, `union(["a","b","c"],["b","c","d"])` returns ["a","b","c","d"]. *Type*: rvalue. + +#### `unique` + +Removes duplicates from strings and arrays. For example, `unique("aabbcc")` returns 'abc', and `unique(["a","a","b","b","c","c"])` returns ["a","b","c"]. *Type*: rvalue. + +#### `unix2dos` + +Returns the DOS version of the given string. Very useful when using a File resource with a cross-platform template. *Type*: rvalue. + +~~~ +file{$config_file: + ensure => file, + content => unix2dos(template('my_module/settings.conf.erb')), +} +~~~ + +See also [dos2unix](#dos2unix). + +#### `upcase` + +Converts an object, array or hash of objects that respond to upcase to uppercase. For example, `upcase('abcd')` returns 'ABCD'. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `uriescape` + +URLEncodes a string or array of strings. Requires either a single string or an array as an input. *Type*: rvalue. + +*Please note:* This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +#### `validate_absolute_path` + +Validates that a given string represents an absolute path in the filesystem. Works for Windows and Unix style paths. + +The following values pass: + +~~~ +$my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' +validate_absolute_path($my_path) +$my_path2 = '/var/lib/puppet' +validate_absolute_path($my_path2) +$my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet'] +validate_absolute_path($my_path3) +$my_path4 = ['/var/lib/puppet','/usr/share/puppet'] +validate_absolute_path($my_path4) +~~~ + +The following values fail, causing compilation to abort: + +~~~ +validate_absolute_path(true) +validate_absolute_path('../var/lib/puppet') +validate_absolute_path('var/lib/puppet') +validate_absolute_path([ 'var/lib/puppet', '/var/foo' ]) +validate_absolute_path([ '/var/lib/puppet', 'var/foo' ]) +$undefined = undef +validate_absolute_path($undefined) +~~~ + +*Type*: statement. + +#### `validate_array` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are array data structures. Aborts catalog compilation if any value fails this check. + +The following values pass: + +~~~ +$my_array = [ 'one', 'two' ] +validate_array($my_array) +~~~ + +The following values fail, causing compilation to abort: + +~~~ +validate_array(true) +validate_array('some_string') +$undefined = undef +validate_array($undefined) +~~~ + +*Type*: statement. + +#### `validate_augeas` + +Performs validation of a string using an Augeas lens. The first argument of this function should be the string to test, and the second argument should be the name of the Augeas lens to use. If Augeas fails to parse the string with the lens, the compilation aborts with a parse error. + +A third optional argument lists paths which should **not** be found in the file. The `$file` variable points to the location of the temporary file being tested in the Augeas tree. + +For example, to make sure your $passwdcontent never contains user `foo`: + +~~~ +validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) +~~~ + +To ensure that no users use the '/bin/barsh' shell: + +~~~ +validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] +~~~ + +You can pass a fourth argument as the error message raised and shown to the user: + +~~~ +validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') +~~~ + +*Type*: statement. + +#### `validate_bool` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are either true or false. Aborts catalog compilation if any value fails this check. + +The following values will pass: + +~~~ +$iamtrue = true +validate_bool(true) +validate_bool(true, true, false, $iamtrue) +~~~ + +The following values will fail, causing compilation to abort: + +~~~ +$some_array = [ true ] +validate_bool("false") +validate_bool("true") +validate_bool($some_array) +~~~ + +*Type*: statement. + +#### `validate_cmd` + +Performs validation of a string with an external command. The first argument of this function should be a string to test, and the second argument should be a path to a test command taking a % as a placeholder for the file path (will default to the end of the command if no % placeholder given). If the command is launched against a tempfile containing the passed string, or returns a non-null value, compilation will abort with a parse error. + +If a third argument is specified, this will be the error message raised and seen by the user. + +~~~ +# Defaults to end of path +validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') +~~~ +~~~ +# % as file location +validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content') +~~~ + +*Type*: statement. + +#### `validate_hash` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are hash data structures. Aborts catalog compilation if any value fails this check. + + The following values will pass: + + ~~~ + $my_hash = { 'one' => 'two' } + validate_hash($my_hash) + ~~~ + + The following values will fail, causing compilation to abort: + + ~~~ + validate_hash(true) + validate_hash('some_string') + $undefined = undef + validate_hash($undefined) + ~~~ + +*Type*: statement. + +#### `validate_integer` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is an integer (or an array of integers). Aborts catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not an integer or array of integers, and if arg 2 and arg 3 are not convertable to an integer. + + The following values will pass: + + ~~~ + validate_integer(1) + validate_integer(1, 2) + validate_integer(1, 1) + validate_integer(1, 2, 0) + validate_integer(2, 2, 2) + validate_integer(2, '', 0) + validate_integer(2, undef, 0) + $foo = undef + validate_integer(2, $foo, 0) + validate_integer([1,2,3,4,5], 6) + validate_integer([1,2,3,4,5], 6, 0) + ~~~ + + * Plus all of the above, but any combination of values passed as strings ('1' or "1"). + * Plus all of the above, but with (correct) combinations of negative integer values. + + The following values will fail, causing compilation to abort: + + ~~~ + validate_integer(true) + validate_integer(false) + validate_integer(7.0) + validate_integer({ 1 => 2 }) + $foo = undef + validate_integer($foo) + validate_integer($foobaridontexist) + + validate_integer(1, 0) + validate_integer(1, true) + validate_integer(1, '') + validate_integer(1, undef) + validate_integer(1, , 0) + validate_integer(1, 2, 3) + validate_integer(1, 3, 2) + validate_integer(1, 3, true) + ~~~ + + * Plus all of the above, but any combination of values passed as strings ('false' or "false"). + * Plus all of the above, but with incorrect combinations of negative integer values. + * Plus all of the above, but with non-integer items in arrays or maximum / minimum argument. + + *Type*: statement. + +#### `validate_ip_address` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the argument is an IP address, regardless of it is an IPv4 or an IPv6 +address. It also validates IP address with netmask. The argument must be given as a string. + +The following values will pass: + + ~~~ + validate_ip_address('0.0.0.0') + validate_ip_address('8.8.8.8') + validate_ip_address('127.0.0.1') + validate_ip_address('194.232.104.150') + validate_ip_address('3ffe:0505:0002::') + validate_ip_address('::1/64') + validate_ip_address('fe80::a00:27ff:fe94:44d6/64') + validate_ip_address('8.8.8.8/32') + ~~~ + +The following values will fail, causing compilation to abort: + + ~~~ + validate_ip_address(1) + validate_ip_address(true) + validate_ip_address(0.0.0.256) + validate_ip_address('::1', {}) + validate_ip_address('0.0.0.0.0') + validate_ip_address('3.3.3') + validate_ip_address('23.43.9.22/64') + validate_ip_address('260.2.32.43') + ~~~ + + +#### `validate_legacy` + +Validates a value against both a specified type and a deprecated validation function. Silently passes if both pass, errors if only one validation passes, and fails if both validations return false. + +Accepts arguments for: +* the type to check the value against, +* the full name of the previous validation function, +* the value to be checked, +* an unspecified number of arguments needed for the previous validation function. + +Example: + +``` +validate_legacy("Optional[String]", "validate_re", "Value to be validated", ["."]) +``` + +This function supports updating modules from Puppet 3 style argument validation (using the stdlib `validate_*` functions) to Puppet 4 data types, without breaking functionality for those depending on Puppet 3 style validation. + +> Note: This function is compatible only with Puppet 4.4.0 (PE 2016.1) and later. + +##### For module users + +If you are running Puppet 4, the `validate_legacy` function can help you find and resolve deprecated Puppet 3 `validate_*` functions. These functions are deprecated as of stdlib version 4.13 and will be removed in a future version of stdlib. + +Puppet 4 allows improved defined type checking using [data types](https://docs.puppet.com/puppet/latest/reference/lang_data.html). Data types avoid some of the problems with Puppet 3's `validate_*` functions, which could sometimes be inconsistent. For example, [validate_numeric](#validate_numeric) unintentionally allowed not only numbers, but also arrays of numbers or strings that looked like numbers. + +If you run Puppet 4 and use modules with deprecated `validate_*` functions, you might encounter deprecation messages. The `validate_legacy` function makes these differences visible and makes it easier to move to the clearer Puppet 4 syntax. + +The deprecation messages you get can vary, depending on the modules and data that you use. These deprecation messages appear by default only in Puppet 4: + +* `Notice: Accepting previously invalid value for target type ''`: This message is informational only. You're using values that are allowed by the new type, but would have been invalid by the old validation function. +* `Warning: This method is deprecated, please use the stdlib validate_legacy function`: The module has not yet upgraded to `validate_legacy`. Use the [deprecation](#deprecation) options to silence warnings for now, or submit a fix with the module's developer. See the information [for module developers](#for-module-developers) below for how to fix the issue. +* `Warning: validate_legacy() expected value, got _`: Your code passes a value that was accepted by the Puppet 3-style validation, but will not be accepted by the next version of the module. Most often, you can fix this by removing quotes from numbers or booleans. +* `Error: Evaluation Error: Error while evaluating a Resource Statement, Evaluation Error: Error while evaluating a Function Call, validate_legacy() expected value, got `: Your code passes a value that is not acceptable to either the new or the old style validation. + +##### For module developers + +The `validate_legacy` function helps you move from Puppet 3 style validation to Puppet 4 validation without breaking functionality your module's users depend on. + +Moving to Puppet 4 type validation allows much better defined type checking using [data types](https://docs.puppet.com/puppet/latest/reference/lang_data.html). Many of Puppet 3's `validate_*` functions have surprising holes in their validation. For example, [validate_numeric](#validate_numeric) allows not only numbers, but also arrays of numbers or strings that look like numbers, without giving you any control over the specifics. + +For each parameter of your classes and defined types, choose a new Puppet 4 data type to use. In most cases, the new data type allows a different set of values than the original `validate_*` function. The situation then looks like this: + +| | `validate_` pass | `validate_` fail | +| ------------ | ---------------- | ---------------- | +| matches type | pass | pass, notice | +| fails type | pass, deprecated | fail | + +The code after the validation still has to handle all possible values for now, but users of your code can change their manifests to pass only values that match the new type. + +For each `validate_*` function in stdlib, there is a matching `Stdlib::Compat::*` type that allows the appropriate set of values. See the documentation in the `types/` directory in the stdlib source code for caveats. + +For example, given a class that should accept only numbers, like this: + +~~~ +class example($value) { + validate_numeric($value) +~~~ + +the resulting validation code looks like this: + +~~~ +class example( + Variant[Stdlib::Compat::Numeric, Numeric] $value +) { + validate_legacy(Numeric, 'validate_numeric', $value) +~~~ + +Here, the type of `$value` is defined as `Variant[Stdlib::Compat::Numeric, Numeric]`, which allows any `Numeric` (the new type), as well as all values previously accepted by `validate_numeric` (through `Stdlib::Compat::Numeric`). + +The call to `validate_legacy` takes care of triggering the correct log or fail message for you. It requires the new type, the previous validation function name, and all arguments to that function. + +If your module still supported Puppet 3, this is a breaking change. Update your `metadata.json` requirements section to indicate that your module no longer supports Puppet 3, and bump the major version of your module. With this change, all existing tests for your module should still pass. Create additional tests for the new possible values. + +As a breaking change, this is also a good time to call [`deprecation`](#deprecation) for any parameters you want to get rid of, or to add additional constraints on your parameters. + +After releasing this version, you can release another breaking change release where you remove all compat types and all calls to `validate_legacy`. At that time, you can also go through your code and remove any leftovers dealing with the previously possible values. + +Always note such changes in your CHANGELOG and README. + +#### `validate_numeric` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is a numeric value (or an array or string of numeric values). Aborts catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not a numeric (Integer or Float) or array of numerics, and if arg 2 and arg 3 are not convertable to a numeric. + + For passing and failing usage, see `validate_integer()`. It is all the same for validate_numeric, yet now floating point values are allowed, too. + +*Type*: statement. + +#### `validate_re` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Performs simple validation of a string against one or more regular expressions. The first argument of this function should be the string to +test, and the second argument should be a stringified regular expression (without the // delimiters) or an array of regular expressions. If none of the regular expressions match the string passed in, compilation aborts with a parse error. + + You can pass a third argument as the error message raised and shown to the user. + + The following strings validate against the regular expressions: + + ~~~ + validate_re('one', '^one$') + validate_re('one', [ '^one', '^two' ]) + ~~~ + + The following string fails to validate, causing compilation to abort: + + ~~~ + validate_re('one', [ '^two', '^three' ]) + ~~~ + + To set the error message: + + ~~~ + validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7') + ~~~ + + Note: Compilation terminates if the first argument is not a string. Always use quotes to force stringification: + + ~~~ + validate_re("${::operatingsystemmajrelease}", '^[57]$') + ~~~ + +*Type*: statement. + +#### `validate_slength` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that the first argument is a string (or an array of strings), and is less than or equal to the length of the second argument. It fails if the first argument is not a string or array of strings, or if the second argument is not convertable to a number. Optionally, a minimum string length can be given as the third argument. + + The following values pass: + + ~~~ + validate_slength("discombobulate",17) + validate_slength(["discombobulate","moo"],17) + validate_slength(["discombobulate","moo"],17,3) + ~~~ + + The following values fail: + + ~~~ + validate_slength("discombobulate",1) + validate_slength(["discombobulate","thermometer"],5) + validate_slength(["discombobulate","moo"],17,10) + ~~~ + +*Type*: statement. + +#### `validate_string` + +**Deprecated. Will be removed in a future version of stdlib. See [`validate_legacy`](#validate_legacy).** + +Validates that all passed values are string data structures. Aborts catalog compilation if any value fails this check. + +The following values pass: + + ~~~ + $my_string = "one two" + validate_string($my_string, 'three') + ~~~ + + The following values fail, causing compilation to abort: + + ~~~ + validate_string(true) + validate_string([ 'some', 'array' ]) + ~~~ + +*Note:* validate_string(undef) will not fail in this version of the functions API (incl. current and future parser). + +Instead, use: + + ~~~ + if $var == undef { + fail('...') + } + ~~~ + +*Type*: statement. + +#### `validate_x509_rsa_key_pair` + +Validates a PEM-formatted X.509 certificate and private key using OpenSSL. +Verifies that the certficate's signature was created from the supplied key. + +Fails catalog compilation if any value fails this check. + +Takes two arguments, the first argument must be a X.509 certificate and the +second must be an RSA private key: + + ~~~ + validate_x509_rsa_key_pair($cert, $key) + ~~~ + +*Type*: statement. + +#### `values` + +Returns the values of a given hash. For example, given `$hash = {'a'=1, 'b'=2, 'c'=3} values($hash)` returns [1,2,3]. + +*Type*: rvalue. + +#### `values_at` + +Finds values inside an array based on location. The first argument is the array you want to analyze, and the second argument can be a combination of: + + * A single numeric index + * A range in the form of 'start-stop' (eg. 4-9) + * An array combining the above + + For example, `values_at(['a','b','c'], 2)` returns ['c']; `values_at(['a','b','c'], ["0-1"])` returns ['a','b']; and `values_at(['a','b','c','d','e'], [0, "2-3"])` returns ['a','c','d']. + +*Type*: rvalue. + +#### `zip` + +Takes one element from first array given and merges corresponding elements from second array given. This generates a sequence of n-element arrays, where *n* is one more than the count of arguments. For example, `zip(['1','2','3'],['4','5','6'])` results in ["1", "4"], ["2", "5"], ["3", "6"]. *Type*: rvalue. + +## Limitations + +As of Puppet Enterprise 3.7, the stdlib module is no longer included in PE. PE users should install the most recent release of stdlib for compatibility with Puppet modules. + +### Version Compatibility + +Versions | Puppet 2.6 | Puppet 2.7 | Puppet 3.x | Puppet 4.x | +:---------------|:-----:|:---:|:---:|:----: +**stdlib 2.x** | **yes** | **yes** | no | no +**stdlib 3.x** | no | **yes** | **yes** | no +**stdlib 4.x** | no | **yes** | **yes** | no +**stdlib 4.6+** | no | **yes** | **yes** | **yes** +**stdlib 5.x** | no | no | **yes** | **yes** + +**stdlib 5.x**: When released, stdlib 5.x will drop support for Puppet 2.7.x. Please see [this discussion](https://github.com/puppetlabs/puppetlabs-stdlib/pull/176#issuecomment-30251414). + +## Development + +Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad hardware, software, and deployment configurations that Puppet is intended to serve. We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more information, see our [module contribution guide](https://docs.puppetlabs.com/forge/contributing.html). + +To report or research a bug with any part of this module, please go to +[http://tickets.puppetlabs.com/browse/MODULES](http://tickets.puppetlabs.com/browse/MODULES). + +## Contributors + +The list of contributors can be found at: [https://github.com/puppetlabs/puppetlabs-stdlib/graphs/contributors](https://github.com/puppetlabs/puppetlabs-stdlib/graphs/contributors). diff --git a/environments/production/modules/stdlib/README_DEVELOPER.markdown b/environments/production/modules/stdlib/README_DEVELOPER.markdown new file mode 100644 index 0000000..04349ed --- /dev/null +++ b/environments/production/modules/stdlib/README_DEVELOPER.markdown @@ -0,0 +1,35 @@ +Puppet Specific Facts +===================== + +Facter is meant to stand alone and apart from Puppet. However, Facter often +runs inside Puppet and all custom facts included in the stdlib module will +almost always be evaluated in the context of Puppet and Facter working +together. + +Still, we don't want to write custom facts that blow up in the users face if +Puppet is not loaded in memory. This is often the case if the user runs +`facter` without also supplying the `--puppet` flag. + +Ah! But Jeff, the custom fact won't be in the `$LOAD_PATH` unless the user +supplies `--facter`! You might say... + +Not (always) true I say! If the user happens to have a CWD of +`/stdlib/lib` then the facts will automatically be evaluated and +blow up. + +In any event, it's pretty easy to write a fact that has no value if Puppet is +not loaded. Simply do it like this: + + Facter.add(:node_vardir) do + setcode do + # This will be nil if Puppet is not available. + Facter::Util::PuppetSettings.with_puppet do + Puppet[:vardir] + end + end + end + +The `Facter::Util::PuppetSettings.with_puppet` method accepts a block and +yields to it only if the Puppet library is loaded. If the Puppet library is +not loaded, then the method silently returns `nil` which Facter interprets as +an undefined fact value. The net effect is that the fact won't be set. diff --git a/environments/production/modules/stdlib/README_SPECS.markdown b/environments/production/modules/stdlib/README_SPECS.markdown new file mode 100644 index 0000000..917b631 --- /dev/null +++ b/environments/production/modules/stdlib/README_SPECS.markdown @@ -0,0 +1,7 @@ +NOTE +==== + +This project's specs depend on puppet core, and thus they require the +`puppetlabs_spec_helper` project. For more information please see the README +in that project, which can be found here: [puppetlabs spec +helper](https://github.com/puppetlabs/puppetlabs_spec_helper) diff --git a/environments/production/modules/stdlib/RELEASE_PROCESS.markdown b/environments/production/modules/stdlib/RELEASE_PROCESS.markdown new file mode 100644 index 0000000..0f9328e --- /dev/null +++ b/environments/production/modules/stdlib/RELEASE_PROCESS.markdown @@ -0,0 +1,24 @@ +# Contributing to this module # + + * Work in a topic branch + * Submit a github pull request + * Address any comments / feeback + * Merge into master using --no-ff + +# Releasing this module # + + * This module adheres to http://semver.org/ + * Look for API breaking changes using git diff vX.Y.Z..master + * If no API breaking changes, the minor version may be bumped. + * If there are API breaking changes, the major version must be bumped. + * If there are only small minor changes, the patch version may be bumped. + * Update the CHANGELOG + * Update the Modulefile + * Commit these changes with a message along the lines of "Update CHANGELOG and + Modulefile for release" + * Create an annotated tag with git tag -a vX.Y.Z -m 'version X.Y.Z' (NOTE the + leading v as per semver.org) + * Push the tag with git push origin --tags + * Build a new package with puppet-module or the rake build task if it exists + * Publish the new package to the forge + * Bonus points for an announcement to puppet-users. diff --git a/environments/production/modules/stdlib/Rakefile b/environments/production/modules/stdlib/Rakefile new file mode 100644 index 0000000..d12d854 --- /dev/null +++ b/environments/production/modules/stdlib/Rakefile @@ -0,0 +1,37 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any? + +PuppetLint.configuration.fail_on_warnings = true +PuppetLint.configuration.send('relative') + +desc 'Generate pooler nodesets' +task :gen_nodeset do + require 'beaker-hostgenerator' + require 'securerandom' + require 'fileutils' + + agent_target = ENV['TEST_TARGET'] + if ! agent_target + STDERR.puts 'TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat-64default."' + agent_target = 'redhat-64default.' + end + + master_target = ENV['MASTER_TEST_TARGET'] + if ! master_target + STDERR.puts 'MASTER_TEST_TARGET environment variable is not set' + STDERR.puts 'setting to default value of "redhat7-64mdcl"' + master_target = 'redhat7-64mdcl' + end + + targets = "#{master_target}-#{agent_target}" + cli = BeakerHostGenerator::CLI.new([targets]) + nodeset_dir = "tmp/nodesets" + nodeset = "#{nodeset_dir}/#{targets}-#{SecureRandom.uuid}.yaml" + FileUtils.mkdir_p(nodeset_dir) + File.open(nodeset, 'w') do |fh| + fh.print(cli.execute) + end + puts nodeset +end diff --git a/environments/production/modules/stdlib/appveyor.yml b/environments/production/modules/stdlib/appveyor.yml new file mode 100644 index 0000000..c87ed7c --- /dev/null +++ b/environments/production/modules/stdlib/appveyor.yml @@ -0,0 +1,40 @@ +version: 1.1.x.{build} +skip_commits: + message: /^\(?doc\)?.*/ +clone_depth: 10 +init: +- SET +- 'mkdir C:\ProgramData\PuppetLabs\code && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0' +- 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0' +environment: + matrix: + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 21 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 21-x64 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 23 + - PUPPET_GEM_VERSION: ~> 4.0 + RUBY_VER: 23-x64 + - PUPPET_GEM_VERSION: 4.2.3 + RUBY_VER: 21-x64 +matrix: + fast_finish: true +install: +- SET PATH=C:\Ruby%RUBY_VER%\bin;%PATH% +- bundle install --jobs 4 --retry 2 --without system_tests +- type Gemfile.lock +build: off +test_script: +- bundle exec puppet -V +- ruby -v +- bundle exec rake spec SPEC_OPTS='--format documentation' +notifications: +- provider: Email + to: + - nobody@nowhere.com + on_build_success: false + on_build_failure: false + on_build_status_changed: false diff --git a/environments/production/modules/stdlib/checksums.json b/environments/production/modules/stdlib/checksums.json new file mode 100644 index 0000000..2a972d4 --- /dev/null +++ b/environments/production/modules/stdlib/checksums.json @@ -0,0 +1,508 @@ +{ + "CHANGELOG.md": "54254f2ff8453a9c05c420820f240f0e", + "CONTRIBUTING.md": "77d0440d7cd4206497f99065c60bed46", + "Gemfile": "064893aff1d6bf890eee483af3a79c9e", + "LICENSE": "3b83ef96387f14655fc854ddc3c6bd57", + "MAINTAINERS.md": "b40ec4f2c16145af6f723c2c34bb2ff9", + "NOTICE": "b2e552587e5969886fdd60481e8b0351", + "README.markdown": "c53e1b16e6eb345241addcb867e8c2a6", + "README_DEVELOPER.markdown": "220a8b28521b5c5d2ea87c4ddb511165", + "README_SPECS.markdown": "82bb4c6abbb711f40778b162ec0070c1", + "RELEASE_PROCESS.markdown": "94b92bc99ac4106ba1a74d5c04e520f9", + "Rakefile": "3851f083966b9bbd6d46e50dba5aa52a", + "appveyor.yml": "7e0ff52cc2abe3fa4f2d8d978bf18ad7", + "examples/file_line.pp": "48f7f162127beffea8f8e3e58db2355a", + "examples/has_interface_with.pp": "d69d520cf3ff4d0b495480afaca359ef", + "examples/has_ip_address.pp": "32f42575e49aa66f0f2398a70ae2a9f4", + "examples/has_ip_network.pp": "bfb8db068c58d77c4dd7ae9697536537", + "examples/init.pp": "b52fd907330ddbd9c3e070cf39f7b317", + "lib/facter/facter_dot_d.rb": "d71e93183a680ac78bc0389fd50470a0", + "lib/facter/package_provider.rb": "539766a71dfb2f65e94a7c91bf413fcf", + "lib/facter/pe_version.rb": "60d47406026c8201e51394227ddf780d", + "lib/facter/puppet_settings.rb": "9438c0839ae28dc52fffb8348ae5124f", + "lib/facter/root_home.rb": "35702ae0c7410ec4d2101113e2f697fa", + "lib/facter/service_provider.rb": "66cc42526eae631e306b397391f1f01c", + "lib/facter/util/puppet_settings.rb": "9f1d2593d0ae56bfca89d4b9266aeee1", + "lib/puppet/functions/deprecation.rb": "59b803eaa15b5a559040497bffc172ae", + "lib/puppet/functions/is_a.rb": "9dad7f8c9b75348cd97aca986ac0b29a", + "lib/puppet/functions/is_absolute_path.rb": "96b217f26d06dbac87a2c6a8cfd2d8c8", + "lib/puppet/functions/is_array.rb": "9292a646010d167417a1936b0b0c17b9", + "lib/puppet/functions/is_bool.rb": "73957f9efd75ed8a7ab867f9de6da117", + "lib/puppet/functions/is_float.rb": "af3bd6bb56878bac8cc4fe4f7564e4f9", + "lib/puppet/functions/is_ip_address.rb": "ee231c66c3e039778bf46702d89815a6", + "lib/puppet/functions/is_ipv4_address.rb": "900d33249906c4daa02aa79cac896548", + "lib/puppet/functions/is_ipv6_address.rb": "568fba9af6a83c8b536fafcda82eb448", + "lib/puppet/functions/is_numeric.rb": "33051800b886cc3b2119826b77c9821a", + "lib/puppet/functions/is_string.rb": "230e9eabc5c9e1d8d5fb7b3c6c12b300", + "lib/puppet/functions/length.rb": "03cf801867a919f801d290424fd2bb8d", + "lib/puppet/functions/type_of.rb": "bec00841aae556993c926ab298bc81cd", + "lib/puppet/functions/validate_absolute_path.rb": "54a610baa115c7505f1b35976b632a8e", + "lib/puppet/functions/validate_array.rb": "9052b0026da174636c276a2512cf5acc", + "lib/puppet/functions/validate_bool.rb": "fe979e402a5a3a19d013ce84b39ef06a", + "lib/puppet/functions/validate_hash.rb": "92ea8fc21bbbf6cc41f6bb9cfcaefce7", + "lib/puppet/functions/validate_integer.rb": "b0982b68a599262da2c6f2e032bc7713", + "lib/puppet/functions/validate_ip_address.rb": "65a12af9a2c2a9c70d820d04d19ec891", + "lib/puppet/functions/validate_ipv4_address.rb": "4a5039b99ac97cc0447faa343b9f7416", + "lib/puppet/functions/validate_ipv6_address.rb": "fbdf685432416505fed27d5647c26f9c", + "lib/puppet/functions/validate_legacy.rb": "d9f115f30c511cef536a821b94826094", + "lib/puppet/functions/validate_numeric.rb": "41b2cc7335395f617c2bfbeac8f579da", + "lib/puppet/functions/validate_re.rb": "42092f592ebf89b8a504b10c900230d8", + "lib/puppet/functions/validate_slength.rb": "3ae6fcc3f60032c923d06ab3e457b84e", + "lib/puppet/functions/validate_string.rb": "cc967a9d0ea156b2208d1760d7f6e1b2", + "lib/puppet/parser/functions/abs.rb": "a8e46ecf4fb378d314916599651cebe5", + "lib/puppet/parser/functions/any2array.rb": "a81e71d6b67a551d38770ba9a1948a75", + "lib/puppet/parser/functions/any2bool.rb": "8bbb74cb81ebdb164e1965415364080b", + "lib/puppet/parser/functions/assert_private.rb": "cb9cd79ed4e3d647a48467dfb6237d5c", + "lib/puppet/parser/functions/base64.rb": "0d121a32fbfe25b06a4e91b0259af91d", + "lib/puppet/parser/functions/basename.rb": "c61952b3f68fd86408c84fca2c3febb1", + "lib/puppet/parser/functions/bool2num.rb": "01b070b02611fda7e1491de0a8cd89fc", + "lib/puppet/parser/functions/bool2str.rb": "c00885242abf381ba482644ded70b688", + "lib/puppet/parser/functions/camelcase.rb": "bd5da699dfe1ec27ffe91b06187c5900", + "lib/puppet/parser/functions/capitalize.rb": "c94c50b0f147a22861c3b93c724e4343", + "lib/puppet/parser/functions/ceiling.rb": "7de4a96c8bb645fd5ce5c38438667228", + "lib/puppet/parser/functions/chomp.rb": "35a16c85bc59dc285baae631da1ae771", + "lib/puppet/parser/functions/chop.rb": "3beb80906fa3c759945a2664fe510b20", + "lib/puppet/parser/functions/clamp.rb": "6920e6c11b575fc65dadb21220c01a7a", + "lib/puppet/parser/functions/concat.rb": "d10e5428917895d4bb90c638b148b7fa", + "lib/puppet/parser/functions/convert_base.rb": "c3b3e59a49318af98dcb88aed7156629", + "lib/puppet/parser/functions/count.rb": "325bbd89a29e6ed0f31e0462d0a0d301", + "lib/puppet/parser/functions/deep_merge.rb": "d83696855578fb81b64b9e92b9c7cc7c", + "lib/puppet/parser/functions/defined_with_params.rb": "80eed5cedb1b5134e6a1cf44e86ae60a", + "lib/puppet/parser/functions/delete.rb": "7af1540fee4285d93b6b28fb490db70a", + "lib/puppet/parser/functions/delete_at.rb": "a2cba60ac86a676d4ed1ff846a917014", + "lib/puppet/parser/functions/delete_regex.rb": "91cd012ef5a636ffe541814ed232b909", + "lib/puppet/parser/functions/delete_undef_values.rb": "52beef9ee37f84ed2278a69ec4383125", + "lib/puppet/parser/functions/delete_values.rb": "b410f5618b4a6158a921acb7b2dc628d", + "lib/puppet/parser/functions/deprecation.rb": "4323210434d36e37977251f906a232b8", + "lib/puppet/parser/functions/difference.rb": "467e44aeaebd0ee0a51c8b89e3769f1f", + "lib/puppet/parser/functions/dig.rb": "1a2a8918f646c13dcb9876a22f9295ab", + "lib/puppet/parser/functions/dig44.rb": "3078b97ee941c261944857373d400ed6", + "lib/puppet/parser/functions/dirname.rb": "8a5579f9a9a13fd737ba65eccf8e6d5a", + "lib/puppet/parser/functions/dos2unix.rb": "be8359a5106a7832be4180e8207dd586", + "lib/puppet/parser/functions/downcase.rb": "02376505a00accd0ce6b148f869c9c86", + "lib/puppet/parser/functions/empty.rb": "6d7d2dde2971f6a40fa913f0af1c610c", + "lib/puppet/parser/functions/enclose_ipv6.rb": "ec39936a9a51dc5cb8ada6eeb829b239", + "lib/puppet/parser/functions/ensure_packages.rb": "0fca8d6dc6608dc16e316fcd39f692b7", + "lib/puppet/parser/functions/ensure_resource.rb": "de703fe63392b939fc2b4392975263de", + "lib/puppet/parser/functions/ensure_resources.rb": "c92d8b69d6354eda24aa3a13d88177b2", + "lib/puppet/parser/functions/flatten.rb": "6a27f5b922a24fec6e823f6f51c98090", + "lib/puppet/parser/functions/floor.rb": "07ba3b1662a14c60fe908b51231355ee", + "lib/puppet/parser/functions/fqdn_rand_string.rb": "9ac5f18e563094aee62ef7586267025d", + "lib/puppet/parser/functions/fqdn_rotate.rb": "2483d17df4e6cb25d92b4e8520f30957", + "lib/puppet/parser/functions/fqdn_uuid.rb": "d357e8837ba2ed8196e926f3697be521", + "lib/puppet/parser/functions/get_module_path.rb": "d4bf50da25c0b98d26b75354fa1bcc45", + "lib/puppet/parser/functions/getparam.rb": "440a9c381b9ad589504e2e9919e83c06", + "lib/puppet/parser/functions/getvar.rb": "0c8c5cef7e158e232a8cf6e42c10d0ff", + "lib/puppet/parser/functions/grep.rb": "c2f2dbdf79d16584579c3a7bc7b5902f", + "lib/puppet/parser/functions/has_interface_with.rb": "db65f5aac94e8fe105c7ca0cd1f66403", + "lib/puppet/parser/functions/has_ip_address.rb": "6ce9e6cdfb499b6ce86bf531848a18be", + "lib/puppet/parser/functions/has_ip_network.rb": "3cc7b923fd3cde5062ed9d0eaaa8155f", + "lib/puppet/parser/functions/has_key.rb": "7cd9728c38f0b0065f832dabd62b0e7e", + "lib/puppet/parser/functions/hash.rb": "c1ebb21cc5b984153a87c252a9854db2", + "lib/puppet/parser/functions/intersection.rb": "87469eb81ab00bbacfd744165beeaeec", + "lib/puppet/parser/functions/is_absolute_path.rb": "1ce9a6d1cd0a79087d73cb879ed04542", + "lib/puppet/parser/functions/is_array.rb": "bbce6768b688bc0f36978ecb0f60f7f4", + "lib/puppet/parser/functions/is_bool.rb": "b8800ff7a11b4e8c03616041e218225f", + "lib/puppet/parser/functions/is_domain_name.rb": "5c6106c070945a605c5adbd1852f0691", + "lib/puppet/parser/functions/is_email_address.rb": "1eb786779743e93a7bb9fe8087b38b8d", + "lib/puppet/parser/functions/is_float.rb": "6257620b98c5099293be7aa4088b88ce", + "lib/puppet/parser/functions/is_function_available.rb": "f13934d6b41561ef54d88cf0da86231b", + "lib/puppet/parser/functions/is_hash.rb": "6f1ccf659c41cb5dab4db9fa89f0b364", + "lib/puppet/parser/functions/is_integer.rb": "a4ae475a5a799c0cc46519e86aed2973", + "lib/puppet/parser/functions/is_ip_address.rb": "2eabe60c213df2e642a03ebfd4852497", + "lib/puppet/parser/functions/is_ipv4_address.rb": "ab854b47367921f657410fbe79054f0b", + "lib/puppet/parser/functions/is_ipv6_address.rb": "2a2be7adbdd5ca23c3e21f00d7d8bac7", + "lib/puppet/parser/functions/is_mac_address.rb": "1af27510d3b9b936384192a4e1dfbf8c", + "lib/puppet/parser/functions/is_numeric.rb": "bfd99f39e2506953ad00178db6fa07ea", + "lib/puppet/parser/functions/is_string.rb": "c1405dd293e9e3c738b83c4ef5aab1ef", + "lib/puppet/parser/functions/join.rb": "2ed4f56d296a4535da142e01b11a126d", + "lib/puppet/parser/functions/join_keys_to_values.rb": "71ad24ac1809739713c461141aedd082", + "lib/puppet/parser/functions/keys.rb": "c10485a3d6c53b6d57a891b9852898de", + "lib/puppet/parser/functions/load_module_metadata.rb": "805c5476a6e7083d133e167129885924", + "lib/puppet/parser/functions/loadjson.rb": "ffecf61ba2ec8011915d37009b1a273e", + "lib/puppet/parser/functions/loadyaml.rb": "668265f14327cba1d1400f2078b7b26b", + "lib/puppet/parser/functions/lstrip.rb": "e9cbd5c2233233940e7344478362fb9f", + "lib/puppet/parser/functions/max.rb": "529eb6ac3d88c03caa788167594bd487", + "lib/puppet/parser/functions/member.rb": "03d618926bbb9efd117950e6078c3878", + "lib/puppet/parser/functions/merge.rb": "f3dcc5c83440cdda2036cce69b61a14b", + "lib/puppet/parser/functions/min.rb": "8d829c8a55c9330ab02f962926221d4f", + "lib/puppet/parser/functions/num2bool.rb": "ddb603cf74f92e16a00f1447a4403429", + "lib/puppet/parser/functions/parsejson.rb": "15165fd3807d9f3d657697fa854d643d", + "lib/puppet/parser/functions/parseyaml.rb": "db54578d9d798ced75952217cf11b737", + "lib/puppet/parser/functions/pick.rb": "bf01f13bbfe2318e7f6a302ac7c4433f", + "lib/puppet/parser/functions/pick_default.rb": "ad3ea60262de408767786d37a54d45dc", + "lib/puppet/parser/functions/prefix.rb": "ece9341c5b232a25c58545718a54e92f", + "lib/puppet/parser/functions/private.rb": "1500a21d5cf19961c5b1d476df892d92", + "lib/puppet/parser/functions/pry.rb": "79a48e45196e4bbf0a3e658781513cf4", + "lib/puppet/parser/functions/pw_hash.rb": "3d540eb4c483cc9c111794ac2d24a4a7", + "lib/puppet/parser/functions/range.rb": "ab19430b6b9737cf56263eb65d80cba1", + "lib/puppet/parser/functions/regexpescape.rb": "6378fdd413237a37c322859877147a50", + "lib/puppet/parser/functions/reject.rb": "689f6a7c961a55fe9dcd240921f4c7f9", + "lib/puppet/parser/functions/reverse.rb": "209e7ef512963251571c515e2d0aee10", + "lib/puppet/parser/functions/rstrip.rb": "f3226709247741825f07d9ec20bd3887", + "lib/puppet/parser/functions/seeded_rand.rb": "2ad22e7613d894ae779c0c5b0e65dade", + "lib/puppet/parser/functions/shell_escape.rb": "13662933244e1af42ddca71ced4ac9e5", + "lib/puppet/parser/functions/shell_join.rb": "b99a23d5e62e2e1b98accde5c22e45c9", + "lib/puppet/parser/functions/shell_split.rb": "5317d71f3a7e293624b57aaca23f57d5", + "lib/puppet/parser/functions/shuffle.rb": "6c0555524ebc9ed5dd8295b836fb7163", + "lib/puppet/parser/functions/size.rb": "c171d46b87e377ebcc710ad5a5925a2b", + "lib/puppet/parser/functions/sort.rb": "0b7d1411decc4a92450828809fad0779", + "lib/puppet/parser/functions/squeeze.rb": "403ea46228a8e45e89c91168ed301fb6", + "lib/puppet/parser/functions/str2bool.rb": "ff82f179970a9429614bd37fa7a1ae2a", + "lib/puppet/parser/functions/str2saltedsha512.rb": "457ab12e4329494ae6276cfa4f20eb23", + "lib/puppet/parser/functions/strftime.rb": "8f15e2e3732b6d1d357a1fa1826800d4", + "lib/puppet/parser/functions/strip.rb": "da0ce253cb63a4863f15f9d145217db5", + "lib/puppet/parser/functions/suffix.rb": "1ce493098f17ea47e9435f994adbc6cd", + "lib/puppet/parser/functions/swapcase.rb": "48cad9bf415b5d79584c8e17ab7a06cc", + "lib/puppet/parser/functions/time.rb": "cd96d1f039f8875af083091e3637190b", + "lib/puppet/parser/functions/to_bytes.rb": "45248fd0bba8cfb24eac830fc0add17a", + "lib/puppet/parser/functions/try_get_value.rb": "09cd079ec5f0e5e2ac862c9ebe0f54fe", + "lib/puppet/parser/functions/type.rb": "4709f7ab8a8aad62d77a3c5d91a3aa08", + "lib/puppet/parser/functions/type3x.rb": "5d0391205bd1cfe8de985a44e8c3e8a9", + "lib/puppet/parser/functions/union.rb": "c02fca3fe102875ba020072b7edee532", + "lib/puppet/parser/functions/unique.rb": "0f79a96896149f7034cd85e31eed1e54", + "lib/puppet/parser/functions/unix2dos.rb": "b1f5087fcaca69d9395094204cce887a", + "lib/puppet/parser/functions/upcase.rb": "e875fc4f03adec1ff3b42d22f177441e", + "lib/puppet/parser/functions/uriescape.rb": "ba78def2cd0e60bdc4412df6c7b891ec", + "lib/puppet/parser/functions/validate_absolute_path.rb": "c4b12e101055380386a235cd2c92ad10", + "lib/puppet/parser/functions/validate_array.rb": "bee8327014714d4cd8dbb5c46611f594", + "lib/puppet/parser/functions/validate_augeas.rb": "61e828e7759ba3e1e563e1fdd68aa80f", + "lib/puppet/parser/functions/validate_bool.rb": "971700229c4b581f67f6fadc9019eb2c", + "lib/puppet/parser/functions/validate_cmd.rb": "7df12370db442eddddcf4dd7a5364b5e", + "lib/puppet/parser/functions/validate_email_address.rb": "a72656cbfeda622cdd5cfdafdf464095", + "lib/puppet/parser/functions/validate_hash.rb": "c2ae6299148ea200f8dfcf9204875182", + "lib/puppet/parser/functions/validate_integer.rb": "65aa35f7450794aaadb6ad2c2e114df7", + "lib/puppet/parser/functions/validate_ip_address.rb": "b23c3d5ce6839e32d0186411147a6a44", + "lib/puppet/parser/functions/validate_ipv4_address.rb": "593e8f832469cb6a48c5f16ee66c3b2d", + "lib/puppet/parser/functions/validate_ipv6_address.rb": "48d3733012818993eae662839183d139", + "lib/puppet/parser/functions/validate_numeric.rb": "0e36d370262b8bdef2f88f0a3cb5b30e", + "lib/puppet/parser/functions/validate_re.rb": "d5963c404e3ac1670553f306221c2655", + "lib/puppet/parser/functions/validate_slength.rb": "6cbcfe15378ca4a780bac786223aacac", + "lib/puppet/parser/functions/validate_string.rb": "8afa7b0dcfe17bfbbb5704ad54664cc2", + "lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb": "f17427dfc42128dc1df0ab04f37942e5", + "lib/puppet/parser/functions/values.rb": "c35978761496406cc3dafdccaa014236", + "lib/puppet/parser/functions/values_at.rb": "f7e6ad2a1126acd4fb5f7fcf9bfc2e2b", + "lib/puppet/parser/functions/zip.rb": "133f3d4c54640844e656e2e6e790318e", + "lib/puppet/provider/file_line/ruby.rb": "2bfa44964be201698186b94a60a5eb31", + "lib/puppet/type/anchor.rb": "bbd36bb49c3b554f8602d8d3df366c0c", + "lib/puppet/type/file_line.rb": "d65885f7f0afe49ee199d6bf873a5daf", + "manifests/init.pp": "9560a09f657d7eebbfdb920cefcc1d4f", + "manifests/stages.pp": "72eb4fa624474faf23b39e57cf1590bd", + "metadata.json": "c75558cd574027ce6326bc8f43d3cf07", + "spec/acceptance/abs_spec.rb": "5b60756b2b4da28314025f51989592d7", + "spec/acceptance/anchor_spec.rb": "0fdbe266d8b7c3dc172e338b978109ba", + "spec/acceptance/any2array_spec.rb": "444cfd34154539d896e5ef1488386372", + "spec/acceptance/base64_spec.rb": "6a1dd4144ba354f9bed14eb70f7d2cba", + "spec/acceptance/bool2num_spec.rb": "edc9cb8b89b95410326475d27ce74bd5", + "spec/acceptance/build_csv.rb": "f28ef587de764ade1513091c4906412c", + "spec/acceptance/capitalize_spec.rb": "4bb6471ec3a8a07260da8e249fae6ccd", + "spec/acceptance/ceiling_spec.rb": "46489eef94aa21bb1560383bb17d7d02", + "spec/acceptance/chomp_spec.rb": "8372fe8a875b1a599a89df1191f43bc0", + "spec/acceptance/chop_spec.rb": "d73d1c7c6a44df65677362bd2899bbc1", + "spec/acceptance/clamp_spec.rb": "8afaae07bf89e0af35474da489fd590f", + "spec/acceptance/concat_spec.rb": "bad5f40e0a501b436ec4264cd278290b", + "spec/acceptance/count_spec.rb": "f8abd435b077edd06ad76a96a2e610c0", + "spec/acceptance/deep_merge_spec.rb": "5f16342cb8c1b52d6a08717a2ef87117", + "spec/acceptance/defined_with_params_spec.rb": "8b47a7255b9d662009217f3cfd17ca03", + "spec/acceptance/delete_at_spec.rb": "b0c853ffe5fc121e051233d62f6e72b3", + "spec/acceptance/delete_spec.rb": "6ccb838a13037a56d1413aecb4fdac00", + "spec/acceptance/delete_undef_values_spec.rb": "7d849a978d3ecdaaf5e922acc6038ad8", + "spec/acceptance/delete_values_spec.rb": "b9f3dbffbb89fec32c6a5b2bd3b20901", + "spec/acceptance/deprecation_spec.rb": "cadc56a94cbc2f13965d698f581f582d", + "spec/acceptance/difference_spec.rb": "2f4e8ddd760f2d4dc9a4fb7b653e982f", + "spec/acceptance/dirname_spec.rb": "76e6b66474f070d8a89f3fe5bd187e85", + "spec/acceptance/downcase_spec.rb": "53f0f5b1867e0fd87ba59833c18b5ca2", + "spec/acceptance/empty_spec.rb": "be8a610f87790a321dbd4cc0542e2885", + "spec/acceptance/ensure_resource_spec.rb": "c0193d79f1db1985d313bedb93a4c7ae", + "spec/acceptance/flatten_spec.rb": "3ab1f9c9e761e5d758eeee7a2e32f295", + "spec/acceptance/floor_spec.rb": "4a20f6fc7142ef9357c8d18a408b5e52", + "spec/acceptance/fqdn_rand_string_spec.rb": "7744b45e282013c5fe637dbaf42f85b9", + "spec/acceptance/fqdn_rotate_spec.rb": "366816bb1d3102f64025d61e22eec79c", + "spec/acceptance/get_module_path_spec.rb": "d5d2258b2345359d1e51a638b97523cb", + "spec/acceptance/getparam_spec.rb": "d4037f1f546cf7a3e7dcabe787e637b5", + "spec/acceptance/getvar_spec.rb": "8451bced69bfd5599a552ddf49976d98", + "spec/acceptance/grep_spec.rb": "5e6650532658915d1004a6e6c0f1229d", + "spec/acceptance/has_interface_with_spec.rb": "e028fc7fc1023293ba32c48c9f46752a", + "spec/acceptance/has_ip_address_spec.rb": "f3443be46277f6084ca8e531562d8ac7", + "spec/acceptance/has_ip_network_spec.rb": "fa7a38450bef7e4408deb6b978d5a42f", + "spec/acceptance/has_key_spec.rb": "68e42fb69d15f27916e7943ab727afc6", + "spec/acceptance/hash_spec.rb": "1e8ff803d76d8f9e506c8a366a93ad90", + "spec/acceptance/intersection_spec.rb": "755c135f67811666b3c5152c5c5349c2", + "spec/acceptance/is_a_spec.rb": "4cdd8df78a6285de21d634c032e5a7c9", + "spec/acceptance/is_array_spec.rb": "1de1fb0b55759d4012033ae3ce0723c2", + "spec/acceptance/is_bool_spec.rb": "9984cd966d81c217f8ff4e252a4a66d1", + "spec/acceptance/is_domain_name_spec.rb": "948dc0c7372027e8f2bb9db85b8d66b2", + "spec/acceptance/is_float_spec.rb": "731d9b71e7b4ee5055339d08b2b5f707", + "spec/acceptance/is_function_available_spec.rb": "413964bfe8e18c34d8809590afcc4118", + "spec/acceptance/is_hash_spec.rb": "9095a364cba82a95a1edd0f92d2218c7", + "spec/acceptance/is_integer_spec.rb": "bc4e7192073531638be9f8a4d391a827", + "spec/acceptance/is_ip_address_spec.rb": "c2ee8738d62b4c9d42b3ef1a1b8573fc", + "spec/acceptance/is_ipv4_address_spec.rb": "09f6f3ad6a1c0a9e5dcfaab34a081ffc", + "spec/acceptance/is_ipv6_address_spec.rb": "d19a98176b2b7367e740d338df08c2ae", + "spec/acceptance/is_mac_address_spec.rb": "b53f033cc4943a19f597895df0b5210a", + "spec/acceptance/is_numeric_spec.rb": "95ecae5adde23e6b61cd090704c4572d", + "spec/acceptance/is_string_spec.rb": "d9ad89b30aff62aea4fb805c991c6abf", + "spec/acceptance/join_keys_to_values_spec.rb": "908f62013e1d797ddc0ba68c0fb7913b", + "spec/acceptance/join_spec.rb": "5609c646859140109af4ae1c63cc69d2", + "spec/acceptance/keys_spec.rb": "15edb2f0a5630dfadd9bd6c038e5cf95", + "spec/acceptance/loadjson_spec.rb": "7bf8eb220ed02b77e9956dade1acfcb3", + "spec/acceptance/loadyaml_spec.rb": "3810faad7411bc1d9c9274671bb68a9a", + "spec/acceptance/lstrip_spec.rb": "27a6e2c9e2afc5b3e0e98301798a80ad", + "spec/acceptance/max_spec.rb": "c39a2006dacdbd24a8acc5dc25f1a9f3", + "spec/acceptance/member_spec.rb": "10b9635c1daf636d66aabea6ff796e6a", + "spec/acceptance/merge_spec.rb": "a1502ebd4d069c5f818f7fd8da79e02d", + "spec/acceptance/min_spec.rb": "a6e73a33255bc202de93fd8fe679c510", + "spec/acceptance/nodesets/centos-7-x64.yml": "a713f3abd3657f0ae2878829badd23cd", + "spec/acceptance/nodesets/debian-8-x64.yml": "d2d2977900989f30086ad251a14a1f39", + "spec/acceptance/nodesets/default.yml": "b42da5a1ea0c964567ba7495574b8808", + "spec/acceptance/nodesets/docker/centos-7.yml": "8a3892807bdd62306ae4774f41ba11ae", + "spec/acceptance/nodesets/docker/debian-8.yml": "ac8e871d1068c96de5e85a89daaec6df", + "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "dc42ee922a96908d85b8f0f08203ce58", + "spec/acceptance/num2bool_spec.rb": "33ab633c5a6aa1e5e9c0cac9ec23c0d5", + "spec/acceptance/parsejson_spec.rb": "56353143de570034f59a87244bb9ff7b", + "spec/acceptance/parseyaml_spec.rb": "a564b2217350398d8a3f1b212d0c2ada", + "spec/acceptance/pick_default_spec.rb": "bc3512e0cc3bc34da5f44ae18573af5d", + "spec/acceptance/pick_spec.rb": "661747652e50bb522ad0c627fb8c3c58", + "spec/acceptance/prefix_spec.rb": "2b9df68e18e16144caa4440fd6acaa01", + "spec/acceptance/pw_hash_spec.rb": "979f0d209f1ef227a31d27a452fc2ed3", + "spec/acceptance/range_spec.rb": "f913a26f4bbe8b9432b6cb80a3b6d1ed", + "spec/acceptance/reject_spec.rb": "c63eb909288cf2210f72d3afa1e07b32", + "spec/acceptance/reverse_spec.rb": "eca705328f8e246a648e3f62434e662a", + "spec/acceptance/rstrip_spec.rb": "bad1e42f66d07b5d09ca1133b392bf6a", + "spec/acceptance/shuffle_spec.rb": "adc420bc01d83dde117b04baa4a5e2d8", + "spec/acceptance/size_spec.rb": "d923ec158f5c2275b9823dd118edd953", + "spec/acceptance/sort_spec.rb": "3ac4acd4233fbe624d926604d58acea6", + "spec/acceptance/squeeze_spec.rb": "2270c35766a5162cd732f0f761f0dc34", + "spec/acceptance/str2bool_spec.rb": "73ca9cbf9079ef52de10f90cb871e80c", + "spec/acceptance/str2saltedsha512_spec.rb": "1e89299c3802ba386589a5133a07dccc", + "spec/acceptance/strftime_spec.rb": "6cdfc95c02af2419eb14d173a23df678", + "spec/acceptance/strip_spec.rb": "711b6b73fce659ab83956c4454500e55", + "spec/acceptance/suffix_spec.rb": "c2ca0539e9e7b71f36ff470d3c18b303", + "spec/acceptance/swapcase_spec.rb": "30290d4378f19fa8bac4bc3347a36298", + "spec/acceptance/time_spec.rb": "74b6d4f21d46ca612a98efed9c48944c", + "spec/acceptance/to_bytes_spec.rb": "ff7d57f85cf1e211b0998235636edfd9", + "spec/acceptance/try_get_value_spec.rb": "8b62b969ece6f02c65f0cf3d365324b1", + "spec/acceptance/type_spec.rb": "909aa4d0c2463b04a3d3ff72cb0d4b42", + "spec/acceptance/union_spec.rb": "41b8705ac890ddd915a5f6c8b33620cd", + "spec/acceptance/unique_spec.rb": "0108f76cc12ecb0551a37a002a9bda85", + "spec/acceptance/upcase_spec.rb": "30721f352edd513a0cc36c94aa0fe760", + "spec/acceptance/uriescape_spec.rb": "69857543c97c35b23094cc6da0828055", + "spec/acceptance/validate_absolute_path_spec.rb": "b7f57e45a4d4d2a187bf9db99c481f98", + "spec/acceptance/validate_array_spec.rb": "ec0db1f08eb8d64fec1025b65b544365", + "spec/acceptance/validate_augeas_spec.rb": "744354dd9726be3624db10935ae2b13f", + "spec/acceptance/validate_bool_spec.rb": "950632bafc509e1965cd6a57646d87f6", + "spec/acceptance/validate_cmd_spec.rb": "1605939801210b1a53dc8873492551e0", + "spec/acceptance/validate_hash_spec.rb": "b79b1ef06301e1b97863281e3c419b0e", + "spec/acceptance/validate_ipv4_address_spec.rb": "547520f1ad1d55c52a257f4fb85ecc33", + "spec/acceptance/validate_ipv6_address_spec.rb": "feedad9317be19409e351011c7b852b9", + "spec/acceptance/validate_re_spec.rb": "bcc18798eccca866883b965baefc3cc9", + "spec/acceptance/validate_slength_spec.rb": "29311e9e910bba57a86be9a4893b43e5", + "spec/acceptance/validate_string_spec.rb": "cd36b419ebb8630618ec795781081101", + "spec/acceptance/values_at_spec.rb": "17e39ac5e094fb05522cb363e12186ab", + "spec/acceptance/values_spec.rb": "25d5abaffd9a108a52cdb156b5120e85", + "spec/acceptance/zip_spec.rb": "30ac38a83f8e92d642a20b53c5b66292", + "spec/aliases/absolute_path_spec.rb": "4326af270230f6d1aef6b1c68a31c53b", + "spec/aliases/absolutepath_spec.rb": "ab9a78b2aa5f6eb102ee648c5f88da61", + "spec/aliases/array_spec.rb": "09c3559333ec646387e4141ed37d671e", + "spec/aliases/bool_spec.rb": "c3f734df3cd334860bba95386b8e8ba6", + "spec/aliases/float_spec.rb": "dda76444d67dcf4e9ab4bf8d1a668b23", + "spec/aliases/hash_spec.rb": "8b0753fbb41a41655235f999a8cd8a17", + "spec/aliases/httpsurl_spec.rb": "49fa0c1da3f055228aaa7ab12471f126", + "spec/aliases/httpurl_spec.rb": "fe92b61091197895e9e0070adcaec1e9", + "spec/aliases/integer_spec.rb": "3d5c27c864e2385251f1c16a46ef717d", + "spec/aliases/ip_address.rb": "297c6577c0763f5121f9ca9c19718dfe", + "spec/aliases/ipv4_spec.rb": "8f3be58256671754f32287609c7a64ba", + "spec/aliases/ipv6_spec.rb": "68bc3bdf8f2dbb049f8f8daf93d78e82", + "spec/aliases/numeric_spec.rb": "a0031bb0085673ec7cc28d9c9b5a72e4", + "spec/aliases/string_spec.rb": "2b4661dbe97d7172b613edd50db91542", + "spec/aliases/unixpath_spec.rb": "1fac6f23e14c3589d26284a081c25c77", + "spec/aliases/windowspath_spec.rb": "4a3aec398b2c6ae86cb6d68ac5e8ab06", + "spec/fixtures/dscacheutil/root": "e1a7622f55f3d1be258c9a5b16b474be", + "spec/fixtures/lsuser/root": "2ed657fa157372a81634539bb1a56be8", + "spec/fixtures/test/manifests/absolute_path.pp": "79e9da69f05adc0e1ed41c20bf14e95f", + "spec/fixtures/test/manifests/absolutepath.pp": "8561fa141028cd38d0b8409cd69a1372", + "spec/fixtures/test/manifests/array.pp": "0e1d4146a9b2dd56063b2cab6f3d7571", + "spec/fixtures/test/manifests/bool.pp": "cf75826f36025bd73ff7f479acf7196d", + "spec/fixtures/test/manifests/ensure_resources.pp": "3265ae7af9382cfe7d9a13527265dcc9", + "spec/fixtures/test/manifests/float.pp": "132f9a63e60bba2fba68be83208c2e28", + "spec/fixtures/test/manifests/hash.pp": "84d647482ef0c7dc35348c13fb0ca23e", + "spec/fixtures/test/manifests/httpsurl.pp": "9047fc382d215ae55c065b0798a82a2d", + "spec/fixtures/test/manifests/httpurl.pp": "7165bf5607a4808f3da3f9baca911240", + "spec/fixtures/test/manifests/integer.pp": "3cb3807714ddcde25cd8eeca61581e3b", + "spec/fixtures/test/manifests/ip_address.pp": "78deea27fe1ff9e85c360eced4d2c256", + "spec/fixtures/test/manifests/ipv4.pp": "b4f0dfe6ae06d4f8b284d24e55f6a6a2", + "spec/fixtures/test/manifests/ipv6.pp": "4764f683b6013556e20d8669b21b5bdc", + "spec/fixtures/test/manifests/numeric.pp": "bffd26d7c0bb53628369f42c14b342cb", + "spec/fixtures/test/manifests/string.pp": "27b69c1eb36250bfbd474a0ef9ba847b", + "spec/fixtures/test/manifests/unixpath.pp": "fd062024efdf731e19026901d3fcc075", + "spec/fixtures/test/manifests/windowspath.pp": "7f942ec8f883a3ac0fcaf960ea24b7d8", + "spec/functions/abs_spec.rb": "7c0ebbd787b788d32b9bb21fe9061a2f", + "spec/functions/any2array_spec.rb": "22b7ce032a34d02b6858360025eb7644", + "spec/functions/any2bool_spec.rb": "d090dde9be26601a38980e8f77fa5970", + "spec/functions/assert_private_spec.rb": "a7e79ac7a1b28610a0e57e38e913c9fa", + "spec/functions/base64_spec.rb": "fa279d9a4f3060ee3f467e35b050d528", + "spec/functions/basename_spec.rb": "4aec9efc4fc6ed35de82d93b34dc0fab", + "spec/functions/bool2num_spec.rb": "6609136ff067b90d41cf27bf8838c3ea", + "spec/functions/bool2str_spec.rb": "52560617234393f960aedb496b49a628", + "spec/functions/camelcase_spec.rb": "4a13d3323535291fef3f40a96710acdb", + "spec/functions/capitalize_spec.rb": "31a8d497b274653d5ede70a0187d4053", + "spec/functions/ceiling_spec.rb": "47bd74569f8979d9195df06a863de93b", + "spec/functions/chomp_spec.rb": "8be3a83a34945dcaa6e8e193cf2751e4", + "spec/functions/chop_spec.rb": "8e1f661e93dd87b8bbeebe293fe16612", + "spec/functions/clamp_spec.rb": "65dab1e9aceb2cb4e41ef9c0c65a7ba3", + "spec/functions/concat_spec.rb": "4161a254ff4c21497e61a62ce84dafe8", + "spec/functions/convert_base_spec.rb": "f031e84b18cb010bc6233be3e4bcff83", + "spec/functions/count_spec.rb": "14ab4e4b10fe33d69d0662357cd4bf8b", + "spec/functions/deep_merge_spec.rb": "5a4e826aaa0a0c0e4247b72e03bdfbd1", + "spec/functions/defined_with_params_spec.rb": "9a0af61ab4bfc380ef4707005344fac2", + "spec/functions/delete_at_spec.rb": "188a3143799bd5cf121982883c7dc1c8", + "spec/functions/delete_regex_spec.rb": "c049a3926aded4b6a804f1c824c57d2f", + "spec/functions/delete_spec.rb": "d7c589404fdc3a6b65da7ecf1f7e0b02", + "spec/functions/delete_undef_values_spec.rb": "a2c138145333e1873ee4bf23d091f86a", + "spec/functions/delete_values_spec.rb": "e20b68a589002f1e2b52873d3dbcfb49", + "spec/functions/deprecation_spec.rb": "f5d7355b687eaaa15c20fdb214671c2a", + "spec/functions/difference_spec.rb": "66ba399c0bace1a131ecdb365e0afd4e", + "spec/functions/dig44_spec.rb": "89631bb7282f7d3c9b75d79f48446c65", + "spec/functions/dig_spec.rb": "61708160da38852c3c78bc8563e0fcb2", + "spec/functions/dirname_spec.rb": "e80baeab6a166284f5069263fb5e37b7", + "spec/functions/dos2unix_spec.rb": "d0cfe5fe19d2bf33320e6f47789826c6", + "spec/functions/downcase_spec.rb": "e2c24d41c6fb840f7b66c5205c942780", + "spec/functions/empty_spec.rb": "e6d06c193869ce8c97d3e67d5c5c5b4f", + "spec/functions/ensure_packages_spec.rb": "81f281f4c4f2146328b65b49ad4e846b", + "spec/functions/ensure_resource_spec.rb": "6461ab093bc10308e1c81b01482c409f", + "spec/functions/flatten_spec.rb": "5d59251fa8da0e4c8a376a513a25bc7a", + "spec/functions/floor_spec.rb": "7d110b1f994432e1c6c7c004a3dedbe4", + "spec/functions/fqdn_rand_string_spec.rb": "950a33e5ad8056fbfe564ec7e3b9a583", + "spec/functions/fqdn_rotate_spec.rb": "83f00ce0c48dec632e1aaa8192bb51cf", + "spec/functions/fqdn_uuid_spec.rb": "1b2980fcbecc4b51bdc6de01e9b001b7", + "spec/functions/get_module_path_spec.rb": "ef22bd80ce5dedb069830d5aa10572c1", + "spec/functions/getparam_spec.rb": "359d7c9349ec0e5666bb38e2d9224621", + "spec/functions/getvar_spec.rb": "3c7884e012bdb596df3d0640533dab96", + "spec/functions/grep_spec.rb": "0e917284860c5f96fdf56301012ba199", + "spec/functions/has_interface_with_spec.rb": "473c000e461c3497f8461eb17cf73430", + "spec/functions/has_ip_address_spec.rb": "7b36b993ea32757e74be9909906bd165", + "spec/functions/has_ip_network_spec.rb": "7086a27623ba91c5ff069397a32172c4", + "spec/functions/has_key_spec.rb": "eaa046db8632d6f76893e4b9df7b3e31", + "spec/functions/hash_spec.rb": "53a059e13781fac6b4f8eb660655d58f", + "spec/functions/intersection_spec.rb": "e906cb8ceb39b644b4b338adcd4dda26", + "spec/functions/is_a_spec.rb": "7d6322426d629e5302c52455d9c4cd55", + "spec/functions/is_array_spec.rb": "26d7eab025f333d2c4a8baf89ef4e36e", + "spec/functions/is_bool_spec.rb": "40569a4cdb50af0cc21ad749ac18e6ef", + "spec/functions/is_domain_name_spec.rb": "4ea3825d5b77a5d75eab8228dcc738f9", + "spec/functions/is_email_address_spec.rb": "699c4573dfd00db8993a18afc0eaf875", + "spec/functions/is_float_spec.rb": "8aedd9e622cd1b861d99523532dc4fc1", + "spec/functions/is_function_available.rb": "f8ab234d536532c3629ff6a5068e7877", + "spec/functions/is_hash_spec.rb": "11563529f0f1f821769edb3131277100", + "spec/functions/is_integer_spec.rb": "21a139f9a87b1561bfec60de26c435a2", + "spec/functions/is_ip_address_spec.rb": "04a15328803bb47ae431eff63aa555df", + "spec/functions/is_ipv4_address_spec.rb": "339b8c79925bb70c0de19468d58ab64a", + "spec/functions/is_ipv6_address_spec.rb": "9743a0f2bfda70ace128d2f476a208b9", + "spec/functions/is_mac_address_spec.rb": "532b8c4c63b37849e7fb9122d163ae3a", + "spec/functions/is_numeric_spec.rb": "cb846f44e8f6d79161686d7b3729967c", + "spec/functions/is_string_spec.rb": "56f34694781dfcf774e43bca06057fec", + "spec/functions/join_keys_to_values_spec.rb": "f99dff6550fdb7c245ed4dcaca0a16a3", + "spec/functions/join_spec.rb": "e190fbce526b3f4f7bb8aac029338ece", + "spec/functions/keys_spec.rb": "fba0bfa928b5684d5c918d6c06edd06d", + "spec/functions/length.rb": "c07a267ccccc4b4edbbfad155dc21475", + "spec/functions/load_module_metadata_spec.rb": "431fbe84d627a5c4398f432caaec96f8", + "spec/functions/loadjson_spec.rb": "76cbe175c90a596892b799d8e4d599e5", + "spec/functions/loadyaml_spec.rb": "60e283bc07adc67bc2c934424bf01b8d", + "spec/functions/lstrip_spec.rb": "892229ce8657490c72fc860fb0dda1b6", + "spec/functions/max_spec.rb": "47de8d59070d8d51b2184731f5d1aa43", + "spec/functions/member_spec.rb": "6a988e6368c1ce090b47fe41efbc041c", + "spec/functions/merge_spec.rb": "56297527d192640bbe82c7ccf1e39815", + "spec/functions/min_spec.rb": "8b38e2a989912406cd2c57dcd3a460c4", + "spec/functions/num2bool_spec.rb": "7c4fd30e41a11b1bd0d9e5233340f16b", + "spec/functions/parsejson_spec.rb": "958db1dde2f2244f458c399e0c3231e0", + "spec/functions/parseyaml_spec.rb": "19a2add90ca08d5a17c4e3aff89fe3c0", + "spec/functions/pick_default_spec.rb": "ab9ef2e9111cfd2709313e0e3c3befa2", + "spec/functions/pick_spec.rb": "786044a71e3bd673212949c12f37b10f", + "spec/functions/prefix_spec.rb": "2402d2ad5108050bb5c20b3ea58b64be", + "spec/functions/private_spec.rb": "f404771c4590a0cd7ce61ddff8f3eb7b", + "spec/functions/pw_hash_spec.rb": "640609cc73b4c8bbcdfc88c3e9797664", + "spec/functions/range_spec.rb": "d3af56cc8a137059423f8738e343ccff", + "spec/functions/regexpescape_spec.rb": "ce027e1b99674f39de8ddffcfed8396e", + "spec/functions/reject_spec.rb": "8ecbca64a9817ebef76be96dcf6cdeeb", + "spec/functions/reverse_spec.rb": "e40754fd34e96ac206308958504ae315", + "spec/functions/rstrip_spec.rb": "748f4587c12b5a590b985f111e282a8d", + "spec/functions/seeded_rand_spec.rb": "eb56445d10962d81ed935b53826da349", + "spec/functions/shell_escape_spec.rb": "bf108573c041f0c228f6d47d367d0aa3", + "spec/functions/shell_join_spec.rb": "92f31bd98d6e8e30e5f758f50bcb8a75", + "spec/functions/shell_split_spec.rb": "430f58733cc7855e56bfc918971b3fc1", + "spec/functions/shuffle_spec.rb": "be5d43184f2ca42db4f452085a4730fd", + "spec/functions/size_spec.rb": "f5e35878331bb12d9639522e38cc81c8", + "spec/functions/sort_spec.rb": "d9533dd37c6263b92895f7eba8485248", + "spec/functions/squeeze_spec.rb": "ffcf3b44cbdd45e14eb1173d2b7d1d99", + "spec/functions/str2bool_spec.rb": "607b25fb0badd0da5acb86c63437c8be", + "spec/functions/str2saltedsha512_spec.rb": "07586b0026757cd39229c12c7221808b", + "spec/functions/strftime_spec.rb": "f1a34fc930940213abfba3095d5d8065", + "spec/functions/strip_spec.rb": "023132b8bd0820da254f74c6fe31bb80", + "spec/functions/suffix_spec.rb": "dfe26a61bf0185291b6ae868e9d0de20", + "spec/functions/swapcase_spec.rb": "90bace1b004aa63d46eb6481c6dce2b1", + "spec/functions/time_spec.rb": "6dc8f5b42cf89345d2de424bfe19be90", + "spec/functions/to_bytes_spec.rb": "b771f8490d922de46a519e407d358139", + "spec/functions/try_get_value_spec.rb": "b917b899f5d29764dd4b1b07e07ec6ce", + "spec/functions/type3x_spec.rb": "eed4ce3a2bc92d14effedefef9690654", + "spec/functions/type_of_spec.rb": "83755d9390b9c00e086a007edff7fd9b", + "spec/functions/type_spec.rb": "7a61b4af7d3d83be590d783a7e5e80f8", + "spec/functions/union_spec.rb": "a290a38e443b856a476d14d4b1dc72fb", + "spec/functions/unique_spec.rb": "97fbcc73a0e0d82e23c723837627c712", + "spec/functions/unix2dos_spec.rb": "628c8a0c608d1fa0dd09bd1b5af97c1f", + "spec/functions/upcase_spec.rb": "4f0461a20c03d618f0c18da39bebcf65", + "spec/functions/uriescape_spec.rb": "1458afbe7e7e11dcaad8d295a7f2be59", + "spec/functions/validate_absolute_path_spec.rb": "5adbf0a0ac8b23e86195deaf3e4bd371", + "spec/functions/validate_array_spec.rb": "3aa76f3731754c637acc94b59c79efb0", + "spec/functions/validate_augeas_spec.rb": "d598e89a23912be9f24d39b809f30b47", + "spec/functions/validate_bool_spec.rb": "d1b43cd294d0588ac6d67d5d3c77a42a", + "spec/functions/validate_cmd_spec.rb": "34a4e623ab0ce7d4c218a837f896c1ea", + "spec/functions/validate_email_address_spec.rb": "1296f9934d102258091379ee07f9a2a8", + "spec/functions/validate_hash_spec.rb": "0b3ac3f7262f13ef14e3df937c8d104c", + "spec/functions/validate_integer_spec.rb": "ec55ab34e3ec4f09722349cd6aac77a6", + "spec/functions/validate_ip_address_spec.rb": "ba8c92e30f1af9e6839a2901e002c3f3", + "spec/functions/validate_ipv4_address_spec.rb": "d5d956651629f95ea531ac03b71e247b", + "spec/functions/validate_ipv6_address_spec.rb": "7ee0d6097666558d8627de424c26ef75", + "spec/functions/validate_legacy_spec.rb": "a1a3af70e71d0a6b848d75c518949b3b", + "spec/functions/validate_numeric_spec.rb": "c2f01b9c9aeae75683563ef7d3951bbb", + "spec/functions/validate_re_spec.rb": "27bcf99cd815b755548cd3979753b2f3", + "spec/functions/validate_slength_spec.rb": "c292023b2e61298499ff1f791fc03005", + "spec/functions/validate_string_spec.rb": "bc6735359ada0b3ccf69f515231e3808", + "spec/functions/validate_x509_rsa_key_pair_spec.rb": "24810bdb8ac473ae1168f1b2c5708170", + "spec/functions/values_at_spec.rb": "866ba43a4b606f6e9e72ac91c07408bd", + "spec/functions/values_spec.rb": "6b8f39665cb8fdb03658d838e50b55b0", + "spec/functions/zip_spec.rb": "986defd43a001ff28fe7c90b0366812e", + "spec/monkey_patches/alias_should_to_must.rb": "b19ee31563afb91a72f9869f9d7362ff", + "spec/monkey_patches/publicize_methods.rb": "c690e444b77c871375d321e413e28ca1", + "spec/spec_helper.rb": "b2db3bc02b4ac2fd5142a6621c641b07", + "spec/spec_helper_acceptance.rb": "cd589056b52031da19d9c31ed86ea922", + "spec/spec_helper_local.rb": "1e3ad3c06a11cbcb813dc1d31973cbda", + "spec/support/shared_data.rb": "9758f8ba2958965d287387a2513dac73", + "spec/unit/ensure_resources_spec.rb": "30cad259a3cc6c73f90209804183c818", + "spec/unit/facter/facter_dot_d_spec.rb": "420339a544851f2c7ee6fa4c651bdce8", + "spec/unit/facter/package_provider_spec.rb": "3a6ba799822fbcabc9adab5880260b7a", + "spec/unit/facter/pe_version_spec.rb": "d0fa6c0d5b01a4b9fd36ed4168635e9f", + "spec/unit/facter/root_home_spec.rb": "036c160d5543f4f3e80a300a3a170b77", + "spec/unit/facter/service_provider_spec.rb": "a97efb411817a44c511cd6cd79d9af8c", + "spec/unit/facter/util/puppet_settings_spec.rb": "6f9df9b10a1b39245ecdf002616a4122", + "spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb": "0145a78254ea716e5e7600d9464318a8", + "spec/unit/puppet/parser/functions/is_absolute_path_spec.rb": "52f8d3b9011fb1fd8a2a429fe8b2ae08", + "spec/unit/puppet/provider/file_line/ruby_spec.rb": "d269e18953fc297786a8393b20842abc", + "spec/unit/puppet/type/anchor_spec.rb": "06a669dffa44d716bf19b4e7f5f1d75d", + "spec/unit/puppet/type/file_line_spec.rb": "83e11166de4abe69cdf0dc7450e79778", + "types/absolutepath.pp": "3ae6f48dd95835df87c5dacea13c88d2", + "types/compat/absolute_path.pp": "6d0e102d1098d85d97c8f13051cb2c10", + "types/compat/array.pp": "43c41022dd743d55c8e9e82ce3ed741c", + "types/compat/bool.pp": "6e0cd7b2a7e1b9efaa0e273937211e58", + "types/compat/float.pp": "fd134a0e0e1abd420cec2107780a7afa", + "types/compat/hash.pp": "3535d98f0f3eaec44d443190f258e9ba", + "types/compat/integer.pp": "e85a20e42b3afaf5ec0718a8d9f9c5e3", + "types/compat/ip_address.pp": "d42c7435de30a469a71b69c45d642da7", + "types/compat/ipv4.pp": "c84eb3764f45b256a09be68afe61b749", + "types/compat/ipv6.pp": "d3c0fada5425d576aeca14ddbee78653", + "types/compat/numeric.pp": "f864539d78e95dabf0bab361b4ccc042", + "types/compat/re.pp": "70b05d4697e61f9af0c212ba063f395d", + "types/compat/string.pp": "b9a179664c85e121aa0277021627d126", + "types/httpsurl.pp": "108225d0273e17d541b239882287ed15", + "types/httpurl.pp": "4ba64825b11af75997c59b5acf07c62d", + "types/unixpath.pp": "5fc80523e7f474dd84056c44fafd9544", + "types/windowspath.pp": "4c1eda331ecb43de98c29ed22d0ad448" +} \ No newline at end of file diff --git a/environments/production/modules/stdlib/examples/file_line.pp b/environments/production/modules/stdlib/examples/file_line.pp new file mode 100644 index 0000000..85b1325 --- /dev/null +++ b/environments/production/modules/stdlib/examples/file_line.pp @@ -0,0 +1,9 @@ +# This is a simple smoke test +# of the file_line resource type. +file { '/tmp/dansfile': + ensure => file, +} -> +file_line { 'dans_line': + line => 'dan is awesome', + path => '/tmp/dansfile', +} diff --git a/environments/production/modules/stdlib/examples/has_interface_with.pp b/environments/production/modules/stdlib/examples/has_interface_with.pp new file mode 100644 index 0000000..a578dd2 --- /dev/null +++ b/environments/production/modules/stdlib/examples/has_interface_with.pp @@ -0,0 +1,9 @@ +include ::stdlib +info('has_interface_with(\'lo\'):', has_interface_with('lo')) +info('has_interface_with(\'loX\'):', has_interface_with('loX')) +info('has_interface_with(\'ipaddress\', \'127.0.0.1\'):', has_interface_with('ipaddress', '127.0.0.1')) +info('has_interface_with(\'ipaddress\', \'127.0.0.100\'):', has_interface_with('ipaddress', '127.0.0.100')) +info('has_interface_with(\'network\', \'127.0.0.0\'):', has_interface_with('network', '127.0.0.0')) +info('has_interface_with(\'network\', \'128.0.0.0\'):', has_interface_with('network', '128.0.0.0')) +info('has_interface_with(\'netmask\', \'255.0.0.0\'):', has_interface_with('netmask', '255.0.0.0')) +info('has_interface_with(\'netmask\', \'256.0.0.0\'):', has_interface_with('netmask', '256.0.0.0')) diff --git a/environments/production/modules/stdlib/examples/has_ip_address.pp b/environments/production/modules/stdlib/examples/has_ip_address.pp new file mode 100644 index 0000000..594143d --- /dev/null +++ b/environments/production/modules/stdlib/examples/has_ip_address.pp @@ -0,0 +1,3 @@ +include ::stdlib +info('has_ip_address(\'192.168.1.256\'):', has_ip_address('192.168.1.256')) +info('has_ip_address(\'127.0.0.1\'):', has_ip_address('127.0.0.1')) diff --git a/environments/production/modules/stdlib/examples/has_ip_network.pp b/environments/production/modules/stdlib/examples/has_ip_network.pp new file mode 100644 index 0000000..1f1130d --- /dev/null +++ b/environments/production/modules/stdlib/examples/has_ip_network.pp @@ -0,0 +1,3 @@ +include ::stdlib +info('has_ip_network(\'127.0.0.0\'):', has_ip_network('127.0.0.0')) +info('has_ip_network(\'128.0.0.0\'):', has_ip_network('128.0.0.0')) diff --git a/environments/production/modules/stdlib/examples/init.pp b/environments/production/modules/stdlib/examples/init.pp new file mode 100644 index 0000000..ad27972 --- /dev/null +++ b/environments/production/modules/stdlib/examples/init.pp @@ -0,0 +1 @@ +include ::stdlib diff --git a/environments/production/modules/stdlib/lib/facter/facter_dot_d.rb b/environments/production/modules/stdlib/lib/facter/facter_dot_d.rb new file mode 100644 index 0000000..5c5fb1f --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/facter_dot_d.rb @@ -0,0 +1,202 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d +# and /etc/puppetlabs/facter/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. +# +# In the case of scripts you can also create a file that +# contains a cache TTL. For foo.sh store the ttl as just +# a number in foo.sh.ttl +# +# The cache is stored in $libdir/facts_dot_d.cache as a mode +# 600 file and will have the end result of not calling your +# fact scripts more often than is needed + +class Facter::Util::DotD + require 'yaml' + + def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache")) + @dir = dir + @cache_file = cache_file + @cache = nil + @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml} + end + + def entries + Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) } + rescue + [] + end + + def fact_type(file) + extension = File.extname(file) + + type = @types[extension] || :unknown + + type = :script if type == :unknown && File.executable?(file) + + return type + end + + def txt_parser(file) + File.readlines(file).each do |line| + if line =~ /^([^=]+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") + end + + def json_parser(file) + begin + require 'json' + rescue LoadError + retry if require 'rubygems' + raise + end + + JSON.load(File.read(file)).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}") + end + + def yaml_parser(file) + require 'yaml' + + YAML.load_file(file).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}") + end + + def script_parser(file) + result = cache_lookup(file) + ttl = cache_time(file) + + unless result + result = Facter::Util::Resolution.exec(file) + + if ttl > 0 + Facter.debug("Updating cache for #{file}") + cache_store(file, result) + cache_save! + end + else + Facter.debug("Using cached data for #{file}") + end + + result.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue StandardError => e + Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + + def cache_save! + cache = load_cache + File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) } + rescue + end + + def cache_store(file, data) + load_cache + + @cache[file] = {:data => data, :stored => Time.now.to_i} + rescue + end + + def cache_lookup(file) + cache = load_cache + + return nil if cache.empty? + + ttl = cache_time(file) + + if cache[file] + now = Time.now.to_i + + return cache[file][:data] if ttl == -1 + return cache[file][:data] if (now - cache[file][:stored]) <= ttl + return nil + else + return nil + end + rescue + return nil + end + + def cache_time(file) + meta = file + ".ttl" + + return File.read(meta).chomp.to_i + rescue + return 0 + end + + def load_cache + unless @cache + if File.exist?(@cache_file) + @cache = YAML.load_file(@cache_file) + else + @cache = {} + end + end + + return @cache + rescue + @cache = {} + return @cache + end + + def create + entries.each do |fact| + type = fact_type(fact) + parser = "#{type}_parser" + + if respond_to?("#{type}_parser") + Facter.debug("Parsing #{fact} using #{parser}") + + send(parser, fact) + end + end + end +end + + +mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/) +if mdata + (major, minor, patch) = mdata.captures.map { |v| v.to_i } + if major < 2 + # Facter 1.7 introduced external facts support directly + unless major == 1 and minor > 6 + Facter::Util::DotD.new("/etc/facter/facts.d").create + Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + + # Windows has a different configuration directory that defaults to a vendor + # specific sub directory of the %COMMON_APPDATA% directory. + if Dir.const_defined? 'COMMON_APPDATA' then + windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') + Facter::Util::DotD.new(windows_facts_dot_d).create + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/facter/package_provider.rb b/environments/production/modules/stdlib/lib/facter/package_provider.rb new file mode 100644 index 0000000..3a9117f --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/package_provider.rb @@ -0,0 +1,21 @@ +# Fact: package_provider +# +# Purpose: Returns the default provider Puppet will choose to manage packages +# on this system +# +# Resolution: Instantiates a dummy package resource and return the provider +# +# Caveats: +# +require 'puppet/type' +require 'puppet/type/package' + +Facter.add(:package_provider) do + setcode do + if defined? Gem and Gem::Version.new(Facter.value(:puppetversion).split(' ')[0]) >= Gem::Version.new('3.6') + Puppet::Type.type(:package).newpackage(:name => 'dummy', :allow_virtual => 'true')[:provider].to_s + else + Puppet::Type.type(:package).newpackage(:name => 'dummy')[:provider].to_s + end + end +end diff --git a/environments/production/modules/stdlib/lib/facter/pe_version.rb b/environments/production/modules/stdlib/lib/facter/pe_version.rb new file mode 100644 index 0000000..c9f2181 --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/pe_version.rb @@ -0,0 +1,58 @@ +# Fact: is_pe, pe_version, pe_major_version, pe_minor_version, pe_patch_version +# +# Purpose: Return various facts about the PE state of the system +# +# Resolution: Uses a regex match against puppetversion to determine whether the +# machine has Puppet Enterprise installed, and what version (overall, major, +# minor, patch) is installed. +# +# Caveats: +# +Facter.add("pe_version") do + setcode do + puppet_ver = Facter.value("puppetversion") + if puppet_ver != nil + pe_ver = puppet_ver.match(/Puppet Enterprise (\d+\.\d+\.\d+)/) + pe_ver[1] if pe_ver + else + nil + end + end +end + +Facter.add("is_pe") do + setcode do + if Facter.value(:pe_version).to_s.empty? then + false + else + true + end + end +end + +Facter.add("pe_major_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[0] + end + end +end + +Facter.add("pe_minor_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[1] + end + end +end + +Facter.add("pe_patch_version") do + confine :is_pe => true + setcode do + if pe_version = Facter.value(:pe_version) + pe_version.to_s.split('.')[2] + end + end +end diff --git a/environments/production/modules/stdlib/lib/facter/puppet_settings.rb b/environments/production/modules/stdlib/lib/facter/puppet_settings.rb new file mode 100644 index 0000000..eac9e97 --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/puppet_settings.rb @@ -0,0 +1,43 @@ +# These facter facts return the value of the Puppet vardir and environment path +# settings for the node running puppet or puppet agent. The intent is to +# enable Puppet modules to automatically have insight into a place where they +# can place variable data, or for modules running on the puppet master to know +# where environments are stored. +# +# The values should be directly usable in a File resource path attribute. +# +begin + require 'facter/util/puppet_settings' +rescue LoadError => e + # puppet apply does not add module lib directories to the $LOAD_PATH (See + # #4248). It should (in the future) but for the time being we need to be + # defensive which is what this rescue block is doing. + rb_file = File.join(File.dirname(__FILE__), 'util', 'puppet_settings.rb') + load rb_file if File.exists?(rb_file) or raise e +end + + +# These will be nil if Puppet is not available. +Facter.add(:puppet_vardir) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:vardir] + end + end +end + +Facter.add(:puppet_environmentpath) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:environmentpath] + end + end +end + +Facter.add(:puppet_server) do + setcode do + Facter::Util::PuppetSettings.with_puppet do + Puppet[:server] + end + end +end diff --git a/environments/production/modules/stdlib/lib/facter/root_home.rb b/environments/production/modules/stdlib/lib/facter/root_home.rb new file mode 100644 index 0000000..87c7657 --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/root_home.rb @@ -0,0 +1,45 @@ +# A facter fact to determine the root home directory. +# This varies on PE supported platforms and may be +# reconfigured by the end user. + +module Facter::Util::RootHome + class << self + def get_root_home + root_ent = Facter::Util::Resolution.exec("getent passwd root") + # The home directory is the sixth element in the passwd entry + # If the platform doesn't have getent, root_ent will be nil and we should + # return it straight away. + root_ent && root_ent.split(":")[5] + end + end +end + +Facter.add(:root_home) do + setcode { Facter::Util::RootHome.get_root_home } +end + +Facter.add(:root_home) do + confine :kernel => :darwin + setcode do + str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root") + hash = {} + str.split("\n").each do |pair| + key,value = pair.split(/:/) + hash[key] = value + end + hash['dir'].strip + end +end + +Facter.add(:root_home) do + confine :kernel => :aix + root_home = nil + setcode do + str = Facter::Util::Resolution.exec("lsuser -c -a home root") + str && str.split("\n").each do |line| + next if line =~ /^#/ + root_home = line.split(/:/)[1] + end + root_home + end +end diff --git a/environments/production/modules/stdlib/lib/facter/service_provider.rb b/environments/production/modules/stdlib/lib/facter/service_provider.rb new file mode 100644 index 0000000..a117921 --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/service_provider.rb @@ -0,0 +1,17 @@ +# Fact: service_provider +# +# Purpose: Returns the default provider Puppet will choose to manage services +# on this system +# +# Resolution: Instantiates a dummy service resource and return the provider +# +# Caveats: +# +require 'puppet/type' +require 'puppet/type/service' + +Facter.add(:service_provider) do + setcode do + Puppet::Type.type(:service).newservice(:name => 'dummy')[:provider].to_s + end +end diff --git a/environments/production/modules/stdlib/lib/facter/util/puppet_settings.rb b/environments/production/modules/stdlib/lib/facter/util/puppet_settings.rb new file mode 100644 index 0000000..1ad9452 --- /dev/null +++ b/environments/production/modules/stdlib/lib/facter/util/puppet_settings.rb @@ -0,0 +1,21 @@ +module Facter + module Util + module PuppetSettings + # This method is intended to provide a convenient way to evaluate a + # Facter code block only if Puppet is loaded. This is to account for the + # situation where the fact happens to be in the load path, but Puppet is + # not loaded for whatever reason. Perhaps the user is simply running + # facter without the --puppet flag and they happen to be working in a lib + # directory of a module. + def self.with_puppet + begin + Module.const_get("Puppet") + rescue NameError + nil + else + yield + end + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/deprecation.rb b/environments/production/modules/stdlib/lib/puppet/functions/deprecation.rb new file mode 100644 index 0000000..39d9bc7 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/deprecation.rb @@ -0,0 +1,29 @@ +# Function to print deprecation warnings, Logs a warning once for a given key. The uniqueness key - can appear once. The msg is the message text including any positional information that is formatted by the user/caller of the method It is affected by the puppet setting 'strict', which can be set to :error (outputs as an error message), :off (no message / error is displayed) and :warning (default, outputs a warning) *Type*: String, String. +# + +Puppet::Functions.create_function(:deprecation) do + dispatch :deprecation do + param 'String', :key + param 'String', :message + end + + def deprecation(key, message) + if defined? Puppet::Pops::PuppetStack.stacktrace() + stacktrace = Puppet::Pops::PuppetStack.stacktrace() + file = stacktrace[0] + line = stacktrace[1] + message = "#{message} at #{file}:#{line}" + end + # depending on configuration setting of strict + case Puppet.settings[:strict] + when :off + # do nothing + when :error + fail("deprecation. #{key}. #{message}") + else + unless ENV['STDLIB_LOG_DEPRECATIONS'] == 'false' + Puppet.deprecation_warning(message, key) + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_a.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_a.rb new file mode 100644 index 0000000..da98b03 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_a.rb @@ -0,0 +1,32 @@ +# Boolean check to determine whether a variable is of a given data type. This is equivalent to the `=~` type checks. +# +# @example how to check a data type +# # check a data type +# foo = 3 +# $bar = [1,2,3] +# $baz = 'A string!' +# +# if $foo.is_a(Integer) { +# notify { 'foo!': } +# } +# if $bar.is_a(Array) { +# notify { 'bar!': } +# } +# if $baz.is_a(String) { +# notify { 'baz!': } +# } +# +# See the documentation for "The Puppet Type System" for more information about types. +# See the `assert_type()` function for flexible ways to assert the type of a value. +# +Puppet::Functions.create_function(:is_a) do + dispatch :is_a do + param 'Any', :value + param 'Type', :type + end + + def is_a(value, type) + # See puppet's lib/puppet/pops/evaluator/evaluator_impl.rb eval_MatchExpression + Puppet::Pops::Types::TypeCalculator.instance?(type, value) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_absolute_path.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_absolute_path.rb new file mode 100644 index 0000000..b61064a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_absolute_path.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_absolute_path) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_absolute_path', "This method is deprecated, please use match expressions with Stdlib::Compat::Absolute_Path instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_absolute_path", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_array.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_array.rb new file mode 100644 index 0000000..a29fe8a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_array.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_array) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_array', "This method is deprecated, please use match expressions with Stdlib::Compat::Array instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_array", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_bool.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_bool.rb new file mode 100644 index 0000000..6e2c22b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_bool.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_bool) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_bool', "This method is deprecated, please use match expressions with Stdlib::Compat::Bool instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_bool", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_float.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_float.rb new file mode 100644 index 0000000..c91aa5d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_float.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_float) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_float', "This method is deprecated, please use match expressions with Stdlib::Compat::Float instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_float", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_ip_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_ip_address.rb new file mode 100644 index 0000000..4c72037 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_ip_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ip_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ip_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ip_address instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ip_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb new file mode 100644 index 0000000..97b01ae --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_ipv4_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ipv4_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ipv4_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ipv4 instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ipv4_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb new file mode 100644 index 0000000..be0c98a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_ipv6_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_ipv6_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_ipv4_address', "This method is deprecated, please use match expressions with Stdlib::Compat::Ipv6 instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_ipv6_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_numeric.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_numeric.rb new file mode 100644 index 0000000..f5e9d41 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_numeric.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_numeric) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_numeric', "This method is deprecated, please use match expressions with Stdlib::Compat::Numeric instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_numeric", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/is_string.rb b/environments/production/modules/stdlib/lib/puppet/functions/is_string.rb new file mode 100644 index 0000000..a05a796 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/is_string.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:is_string) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'is_string', "This method is deprecated, please use match expressions with Stdlib::Compat::String instead. They are described at https://docs.puppet.com/puppet/latest/reference/lang_data_type.html#match-expressions.") + scope.send("function_is_string", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/length.rb b/environments/production/modules/stdlib/lib/puppet/functions/length.rb new file mode 100644 index 0000000..86e735c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/length.rb @@ -0,0 +1,14 @@ +#A function to eventually replace the old size() function for stdlib - The original size function did not handle Puppets new type capabilities, so this function is a Puppet 4 compatible solution. +Puppet::Functions.create_function(:length) do + dispatch :length do + param 'Variant[String,Array,Hash]', :value + end + def length(value) + if value.is_a?(String) + result = value.length + elsif value.is_a?(Array) || value.is_a?(Hash) + result = value.size + end + return result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/type_of.rb b/environments/production/modules/stdlib/lib/puppet/functions/type_of.rb new file mode 100644 index 0000000..01f1f49 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/type_of.rb @@ -0,0 +1,19 @@ +# Returns the type when passed a value. +# +# @example how to compare values' types +# # compare the types of two values +# if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") } +# @example how to compare against an abstract type +# unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") } +# unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") } +# +# See the documentation for "The Puppet Type System" for more information about types. +# See the `assert_type()` function for flexible ways to assert the type of a value. +# +# The built-in type() function in puppet is generally preferred over this function +# this function is provided for backwards compatibility. +Puppet::Functions.create_function(:type_of) do + def type_of(value) + Puppet::Pops::Types::TypeCalculator.infer_set(value) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb new file mode 100644 index 0000000..a3c696d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_absolute_path.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_absolute_path) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_absolute_path', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_Path. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_absolute_path", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_array.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_array.rb new file mode 100644 index 0000000..f59c6b4 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_array.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_array) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_array', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_array", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_bool.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_bool.rb new file mode 100644 index 0000000..5cfb2ac --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_bool.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_bool) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_bool', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_bool", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_hash.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_hash.rb new file mode 100644 index 0000000..89ad9ab --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_hash.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_hash) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_hash', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Hash. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_hash", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_integer.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_integer.rb new file mode 100644 index 0000000..475ea0f --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_integer.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_integer) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_integer', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_integer", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_ip_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_ip_address.rb new file mode 100644 index 0000000..1521c08 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_ip_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ip_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ip_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_Address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ip_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb new file mode 100644 index 0000000..fe66ab3 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv4_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ipv4_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ipv4_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4_Address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ipv4_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb new file mode 100644 index 0000000..7cc3cbd --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_ipv6_address.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_ipv6_address) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_ipv6_address', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6_address. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_ipv6_address", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_legacy.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_legacy.rb new file mode 100644 index 0000000..c9d1f56 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_legacy.rb @@ -0,0 +1,62 @@ +Puppet::Functions.create_function(:validate_legacy) do + # The function checks a value against both the target_type (new) and the previous_validation function (old). + + dispatch :validate_legacy do + param 'Any', :scope + param 'Type', :target_type + param 'String', :function_name + param 'Any', :value + repeated_param 'Any', :args + end + + dispatch :validate_legacy_s do + param 'Any', :scope + param 'String', :type_string + param 'String', :function_name + param 'Any', :value + repeated_param 'Any', :args + end + + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + + def validate_legacy_s(scope, type_string, *args) + t = Puppet::Pops::Types::TypeParser.new.parse(type_string, scope) + validate_legacy(scope, t, *args) + end + + def validate_legacy(scope, target_type, function_name, value, *prev_args) + if assert_type(target_type, value) + if previous_validation(scope, function_name, value, *prev_args) + # Silently passes + else + Puppet.notice("Accepting previously invalid value for target type '#{target_type}'") + end + else + inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value) + error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch("validate_legacy(#{function_name})", target_type, inferred_type) + if previous_validation(scope, function_name, value, *prev_args) + call_function('deprecation', 'validate_legacy', error_msg) + else + call_function('fail', error_msg) + end + end + end + + def previous_validation(scope, function_name, value, *prev_args) + # Call the previous validation function and catch any errors. Return true if no errors are thrown. + begin + scope.send("function_#{function_name}".to_s, [value, *prev_args]) + true + rescue Puppet::ParseError + false + end + end + + def assert_type(type, value) + Puppet::Pops::Types::TypeCalculator.instance?(type, value) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_numeric.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_numeric.rb new file mode 100644 index 0000000..3052d35 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_numeric.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_numeric) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_numeric', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_numeric", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_re.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_re.rb new file mode 100644 index 0000000..19443a8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_re.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_re) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_re', "This method is deprecated, please use the stdlib validate_legacy function, with Pattern[]. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_re", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_slength.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_slength.rb new file mode 100644 index 0000000..584232a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_slength.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_slength) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_slength', "This method is deprecated, please use the stdlib validate_legacy function, with String[]. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_slength", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/functions/validate_string.rb b/environments/production/modules/stdlib/lib/puppet/functions/validate_string.rb new file mode 100644 index 0000000..91ff004 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/functions/validate_string.rb @@ -0,0 +1,15 @@ +Puppet::Functions.create_function(:validate_string) do + dispatch :deprecation_gen do + param 'Any', :scope + repeated_param 'Any', :args + end + # Workaround PUP-4438 (fixed: https://github.com/puppetlabs/puppet/commit/e01c4dc924cd963ff6630008a5200fc6a2023b08#diff-c937cc584953271bb3d3b3c2cb141790R221) to support puppet < 4.1.0 and puppet < 3.8.1. + def call(scope, *args) + manipulated_args = [scope] + args + self.class.dispatcher.dispatch(self, scope, manipulated_args) + end + def deprecation_gen(scope, *args) + call_function('deprecation', 'validate_string', "This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.") + scope.send("function_validate_string", args) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/abs.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/abs.rb new file mode 100644 index 0000000..222a902 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/abs.rb @@ -0,0 +1,34 @@ +# +# abs.rb +# + +module Puppet::Parser::Functions + newfunction(:abs, :type => :rvalue, :doc => <<-EOS + Returns the absolute value of a number, for example -34.56 becomes + 34.56. Takes a single integer and float value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "abs(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + # Numbers in Puppet are often string-encoded which is troublesome ... + if value.is_a?(String) + if value.match(/^-?(?:\d+)(?:\.\d+){1}$/) + value = value.to_f + elsif value.match(/^-?\d+$/) + value = value.to_i + else + raise(Puppet::ParseError, 'abs(): Requires float or integer to work with') + end + end + + # We have numeric value to handle ... + result = value.abs + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/any2array.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/any2array.rb new file mode 100644 index 0000000..e71407e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/any2array.rb @@ -0,0 +1,33 @@ +# +# any2array.rb +# + +module Puppet::Parser::Functions + newfunction(:any2array, :type => :rvalue, :doc => <<-EOS +This converts any object to an array containing that object. Empty argument +lists are converted to an empty array. Arrays are left untouched. Hashes are +converted to arrays of alternating keys and values. + EOS + ) do |arguments| + + if arguments.empty? + return [] + end + + if arguments.length == 1 + if arguments[0].kind_of?(Array) + return arguments[0] + elsif arguments[0].kind_of?(Hash) + result = [] + arguments[0].each do |key, value| + result << key << value + end + return result + end + end + + return arguments + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/any2bool.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/any2bool.rb new file mode 100644 index 0000000..17612bf --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/any2bool.rb @@ -0,0 +1,54 @@ +# +# any2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:any2bool, :type => :rvalue, :doc => <<-EOS +This converts 'anything' to a boolean. In practise it does the following: + +* Strings such as Y,y,1,T,t,TRUE,yes,'true' will return true +* Strings such as 0,F,f,N,n,FALSE,no,'false' will return false +* Booleans will just return their original value +* Number (or a string representation of a number) > 0 will return true, otherwise false +* undef will return false +* Anything else will return true + EOS + ) do |arguments| + + raise(Puppet::ParseError, "any2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + # If argument is already Boolean, return it + if !!arguments[0] == arguments[0] + return arguments[0] + end + + arg = arguments[0] + + if arg == nil + return false + end + + if arg == :undef + return false + end + + valid_float = !!Float(arg) rescue false + + if arg.is_a?(Numeric) + return function_num2bool( [ arguments[0] ] ) + end + + if arg.is_a?(String) + if valid_float + return function_num2bool( [ arguments[0] ] ) + else + return function_str2bool( [ arguments[0] ] ) + end + end + + return true + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/assert_private.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/assert_private.rb new file mode 100644 index 0000000..62e2c6b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/assert_private.rb @@ -0,0 +1,28 @@ +# +# assert_private.rb +# + +module Puppet::Parser::Functions + newfunction(:assert_private, :doc => <<-'EOS' + Sets the current class or definition as private. + Calling the class or definition from outside the current module will fail. + EOS + ) do |args| + + raise(Puppet::ParseError, "assert_private(): Wrong number of arguments given (#{args.size}}) for 0 or 1)") if args.size > 1 + + scope = self + if scope.lookupvar('module_name') != scope.lookupvar('caller_module_name') + message = nil + if args[0] and args[0].is_a? String + message = args[0] + else + manifest_name = scope.source.name + manifest_type = scope.source.type + message = (manifest_type.to_s == 'hostclass') ? 'Class' : 'Definition' + message += " #{manifest_name} is private" + end + raise(Puppet::ParseError, message) + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/base64.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/base64.rb new file mode 100644 index 0000000..d610146 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/base64.rb @@ -0,0 +1,71 @@ + +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +module Puppet::Parser::Functions + + newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + + Base64 encode or decode a string based on the command and the string submitted + + Usage: + + $encodestring = base64('encode', 'thestring') + $decodestring = base64('decode', 'dGhlc3RyaW5n') + + # explicitly define encode/decode method: default, strict, urlsafe + $method = 'default' + $encodestring = base64('encode', 'thestring', $method) + $decodestring = base64('decode', 'dGhlc3RyaW5n', $method) + + ENDHEREDOC + + require 'base64' + + raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be >= 2)") unless args.length >= 2 + + actions = ['encode','decode'] + + unless actions.include?(args[0]) + raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'") + end + + unless args[1].is_a?(String) + raise Puppet::ParseError, ("base64(): the second argument must be a string to base64") + end + + method = ['default','strict','urlsafe'] + + if args.length <= 2 + chosenMethod = 'default' + else + chosenMethod = args[2] + end + + unless method.include?(chosenMethod) + raise Puppet::ParseError, ("base64(): the third argument must be one of 'default', 'strict', or 'urlsafe'") + end + + case args[0] + when 'encode' + case chosenMethod + when 'default' + result = Base64.encode64(args[1]) + when 'strict' + result = Base64.strict_encode64(args[1]) + when 'urlsafe' + result = Base64.urlsafe_encode64(args[1]) + end + when 'decode' + case chosenMethod + when 'default' + result = Base64.decode64(args[1]) + when 'strict' + result = Base64.strict_decode64(args[1]) + when 'urlsafe' + result = Base64.urlsafe_decode64(args[1]) + end + end + + return result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/basename.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/basename.rb new file mode 100644 index 0000000..f7e4438 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/basename.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:basename, :type => :rvalue, :doc => <<-EOS + Strips directory (and optional suffix) from a filename + EOS + ) do |arguments| + + if arguments.size < 1 then + raise(Puppet::ParseError, "basename(): No arguments given") + elsif arguments.size > 2 then + raise(Puppet::ParseError, "basename(): Too many arguments given (#{arguments.size})") + else + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'basename(): Requires string as first argument') + end + + if arguments.size == 1 then + rv = File.basename(arguments[0]) + elsif arguments.size == 2 then + + unless arguments[1].is_a?(String) + raise(Puppet::ParseError, 'basename(): Requires string as second argument') + end + + rv = File.basename(arguments[0], arguments[1]) + end + + end + + return rv + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2num.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2num.rb new file mode 100644 index 0000000..92e4dde --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2num.rb @@ -0,0 +1,25 @@ +# +# bool2num.rb +# + +module Puppet::Parser::Functions + newfunction(:bool2num, :type => :rvalue, :doc => <<-EOS + Converts a boolean to a number. Converts the values: + false, f, 0, n, and no to 0 + true, t, 1, y, and yes to 1 + Requires a single boolean or string as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "bool2num(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = function_str2bool([arguments[0]]) + + # We have real boolean values as well ... + result = value ? 1 : 0 + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2str.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2str.rb new file mode 100644 index 0000000..37d4a4e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/bool2str.rb @@ -0,0 +1,44 @@ +# +# bool2str.rb +# + +module Puppet::Parser::Functions + newfunction(:bool2str, :type => :rvalue, :doc => <<-EOS + Converts a boolean to a string using optionally supplied arguments. The + optional second and third arguments represent what true and false will be + converted to respectively. If only one argument is given, it will be + converted from a boolean to a string containing 'true' or 'false'. + + *Examples:* + + bool2str(true) => 'true' + bool2str(true, 'yes', 'no') => 'yes' + bool2str(false, 't', 'f') => 'f' + + Requires a single boolean as an input. + EOS + ) do |arguments| + + unless arguments.size == 1 or arguments.size == 3 + raise(Puppet::ParseError, "bool2str(): Wrong number of arguments given (#{arguments.size} for 3)") + end + + value = arguments[0] + true_string = arguments[1] || 'true' + false_string = arguments[2] || 'false' + klass = value.class + + # We can have either true or false, and nothing else + unless [FalseClass, TrueClass].include?(klass) + raise(Puppet::ParseError, 'bool2str(): Requires a boolean to work with') + end + + unless [true_string, false_string].all?{|x| x.kind_of?(String)} + raise(Puppet::ParseError, "bool2str(): Requires strings to convert to" ) + end + + return value ? true_string : false_string + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/camelcase.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/camelcase.rb new file mode 100644 index 0000000..085b3c6 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/camelcase.rb @@ -0,0 +1,32 @@ +# +# camelcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:camelcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to camel case. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "camelcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + klass = value.class + + unless [Array, String].include?(klass) + raise(Puppet::ParseError, 'camelcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.split('_').map{|e| e.capitalize}.join : i } + else + result = value.split('_').map{|e| e.capitalize}.join + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/capitalize.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/capitalize.rb new file mode 100644 index 0000000..096bc5d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/capitalize.rb @@ -0,0 +1,30 @@ +# +# capitalize.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:capitalize, :type => :rvalue, :doc => <<-EOS + Capitalizes the first letter of a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "capitalize(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'capitalize(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.capitalize : i } + else + result = value.capitalize + end + + return result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/ceiling.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/ceiling.rb new file mode 100644 index 0000000..bec4266 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/ceiling.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions + newfunction(:ceiling, :type => :rvalue, :doc => <<-EOS + Returns the smallest integer greater or equal to the argument. + Takes a single numeric value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "ceiling(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + begin + arg = Float(arguments[0]) + rescue TypeError, ArgumentError => e + raise(Puppet::ParseError, "ceiling(): Wrong argument type given (#{arguments[0]} for Numeric)") + end + + raise(Puppet::ParseError, "ceiling(): Wrong argument type given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false + + arg.ceil + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/chomp.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/chomp.rb new file mode 100644 index 0000000..f9da50f --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/chomp.rb @@ -0,0 +1,32 @@ +# +# chomp.rb +# + +module Puppet::Parser::Functions + newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS' + Removes the record separator from the end of a string or an array of + strings, for example `hello\n` becomes `hello`. + Requires a single string or array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "chomp(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'chomp(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.chomp : i } + else + result = value.chomp + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/chop.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/chop.rb new file mode 100644 index 0000000..809349d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/chop.rb @@ -0,0 +1,34 @@ +# +# chop.rb +# + +module Puppet::Parser::Functions + newfunction(:chop, :type => :rvalue, :doc => <<-'EOS' + Returns a new string with the last character removed. If the string ends + with `\r\n`, both characters are removed. Applying chop to an empty + string returns an empty string. If you wish to merely remove record + separators then you should use the `chomp` function. + Requires a string or array of strings as input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "chop(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'chop(): Requires either an array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.chop : i } + else + result = value.chop + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/clamp.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/clamp.rb new file mode 100644 index 0000000..c4503fe --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/clamp.rb @@ -0,0 +1,29 @@ +# +# clamp.rb +# + +module Puppet::Parser::Functions + newfunction(:clamp, :type => :rvalue, :arity => -2, :doc => <<-EOS + Clamps value to a range. + EOS + ) do |args| + + args.flatten! + + raise(Puppet::ParseError, 'clamp(): Wrong number of arguments, need three to clamp') if args.size != 3 + + # check values out + args.each do |value| + case [value.class] + when [String] + raise(Puppet::ParseError, "clamp(): Required explicit numeric (#{value}:String)") unless value =~ /^\d+$/ + when [Hash] + raise(Puppet::ParseError, "clamp(): The Hash type is not allowed (#{value})") + end + end + + # convert to numeric each element + # then sort them and get a middle value + args.map{ |n| n.to_i }.sort[1] + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/concat.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/concat.rb new file mode 100644 index 0000000..0a49cfe --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/concat.rb @@ -0,0 +1,40 @@ +# +# concat.rb +# + +module Puppet::Parser::Functions + newfunction(:concat, :type => :rvalue, :doc => <<-EOS +Appends the contents of multiple arrays into array 1. + +*Example:* + + concat(['1','2','3'],['4','5','6'],['7','8','9']) + +Would result in: + + ['1','2','3','4','5','6','7','8','9'] + EOS + ) do |arguments| + + # Check that more than 2 arguments have been given ... + raise(Puppet::ParseError, "concat(): Wrong number of arguments given (#{arguments.size} for < 2)") if arguments.size < 2 + + a = arguments[0] + + # Check that the first parameter is an array + unless a.is_a?(Array) + raise(Puppet::ParseError, 'concat(): Requires array to work with') + end + + result = a + arguments.shift + + arguments.each do |x| + result = result + (x.is_a?(Array) ? x : [x]) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/convert_base.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/convert_base.rb new file mode 100644 index 0000000..0fcbafe --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/convert_base.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:convert_base, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| + + Converts a given integer or base 10 string representing an integer to a specified base, as a string. + + Usage: + + $binary_repr = convert_base(5, 2) # $binary_repr is now set to "101" + $hex_repr = convert_base("254", "16") # $hex_repr is now set to "fe" + + ENDHEREDOC + + raise Puppet::ParseError, ("convert_base(): First argument must be either a string or an integer") unless (args[0].is_a?(Integer) or args[0].is_a?(String)) + raise Puppet::ParseError, ("convert_base(): Second argument must be either a string or an integer") unless (args[1].is_a?(Integer) or args[1].is_a?(String)) + + if args[0].is_a?(String) + raise Puppet::ParseError, ("convert_base(): First argument must be an integer or a string corresponding to an integer in base 10") unless args[0] =~ /^[0-9]+$/ + end + + if args[1].is_a?(String) + raise Puppet::ParseError, ("convert_base(): First argument must be an integer or a string corresponding to an integer in base 10") unless args[1] =~ /^[0-9]+$/ + end + + number_to_convert = args[0] + new_base = args[1] + + number_to_convert = number_to_convert.to_i() + new_base = new_base.to_i() + + raise Puppet::ParseError, ("convert_base(): base must be at least 2 and must not be greater than 36") unless new_base >= 2 and new_base <= 36 + + return number_to_convert.to_s(new_base) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/count.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/count.rb new file mode 100644 index 0000000..cef2637 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/count.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS +Takes an array as first argument and an optional second argument. +Count the number of elements in array that matches second argument. +If called with only an array it counts the number of elements that are not nil/undef. + EOS + ) do |args| + + if (args.size > 2) then + raise(ArgumentError, "count(): Wrong number of arguments given #{args.size} for 1 or 2.") + end + + collection, item = args + + if item then + collection.count item + else + collection.count { |obj| obj != nil && obj != :undef && obj != '' } + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb new file mode 100644 index 0000000..6df32e9 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb @@ -0,0 +1,44 @@ +module Puppet::Parser::Functions + newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Recursively merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } + + When there is a duplicate key that is a hash, they are recursively merged. + When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + deep_merge = Proc.new do |hash1,hash2| + hash1.merge(hash2) do |key,old_value,new_value| + if old_value.is_a?(Hash) && new_value.is_a?(Hash) + deep_merge.call(old_value, new_value) + else + new_value + end + end + end + + result = Hash.new + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + # If the argument was not a hash, skip it. + unless arg.is_a?(Hash) + raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments" + end + + result = deep_merge.call(result, arg) + end + return( result ) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb new file mode 100644 index 0000000..99687ae --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb @@ -0,0 +1,38 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:defined_with_params, + :type => :rvalue, + :doc => <<-'ENDOFDOC' +Takes a resource reference and an optional hash of attributes. + +Returns true if a resource with the specified attributes has already been added +to the catalog, and false otherwise. + + user { 'dan': + ensure => present, + } + + if ! defined_with_params(User[dan], {'ensure' => 'present' }) { + user { 'dan': ensure => present, } + } +ENDOFDOC +) do |vals| + reference, params = vals + raise(ArgumentError, 'Must specify a reference') unless reference + if (! params) || params == '' + params = {} + end + ret = false + if resource = findresource(reference.to_s) + matches = params.collect do |key, value| + # eql? avoids bugs caused by monkeypatching in puppet + resource_is_undef = resource[key].eql?(:undef) || resource[key].nil? + value_is_undef = value.eql?(:undef) || value.nil? + (resource_is_undef && value_is_undef) || (resource[key] == value) + end + ret = params.empty? || !matches.include?(false) + end + Puppet.debug("Resource #{reference} was not determined to be defined") + ret +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/delete.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete.rb new file mode 100644 index 0000000..9dd5164 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete.rb @@ -0,0 +1,43 @@ +# +# delete.rb +# + +module Puppet::Parser::Functions + newfunction(:delete, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given element from an array, substring from a +string, or key from a hash. + +*Examples:* + + delete(['a','b','c','b'], 'b') + Would return: ['a','c'] + + delete({'a'=>1,'b'=>2,'c'=>3}, 'b') + Would return: {'a'=>1,'c'=>3} + + delete({'a'=>1,'b'=>2,'c'=>3}, ['b','c']) + Would return: {'a'=>1} + + delete('abracadabra', 'bra') + Would return: 'acada' + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete(): Wrong number of arguments given #{arguments.size} for 2") unless arguments.size == 2 + + collection = arguments[0].dup + Array(arguments[1]).each do |item| + case collection + when Array, Hash + collection.delete item + when String + collection.gsub! item, '' + else + raise(TypeError, "delete(): First argument must be an Array, String, or Hash. Given an argument of class #{collection.class}.") + end + end + collection + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_at.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_at.rb new file mode 100644 index 0000000..daf3721 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_at.rb @@ -0,0 +1,46 @@ +# +# delete_at.rb +# + +module Puppet::Parser::Functions + newfunction(:delete_at, :type => :rvalue, :doc => <<-EOS +Deletes a determined indexed value from an array. + +*Examples:* + + delete_at(['a','b','c'], 1) + +Would return: ['a','c'] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_at(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'delete_at(): Requires array to work with') + end + + index = arguments[1] + + if index.is_a?(String) and not index.match(/^\d+$/) + raise(Puppet::ParseError, 'delete_at(): You must provide non-negative numeric index') + end + + result = array.clone + + # Numbers in Puppet are often string-encoded which is troublesome ... + index = index.to_i + + if index > result.size - 1 # First element is at index 0 is it not? + raise(Puppet::ParseError, 'delete_at(): Given index exceeds size of array given') + end + + result.delete_at(index) # We ignore the element that got deleted ... + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb new file mode 100644 index 0000000..e5dc1fd --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_regex.rb @@ -0,0 +1,44 @@ +# +# delete_regex.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:delete_regex, :type => :rvalue, :doc => <<-EOS +deletes all instances of a given element that match a regular expression +from an array or key from a hash. Multiple regular expressions are assumed +to be matched as an OR. + +*Examples:* + + delete_regex(['a','b','c','b'], 'b') + Would return: ['a','c'] + + delete_regex(['a','b','c','b'], ['b', 'c']) + Would return: ['a'] + + delete_regex({'a'=>1,'b'=>2,'c'=>3}, 'b') + Would return: {'a'=>1,'c'=>3} + + delete_regex({'a'=>1,'b'=>2,'c'=>3}, '^a$') + Would return: {'b'=>2,'c'=>3} + + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_regex(): Wrong number of arguments given #{arguments.size} for 2") unless arguments.size == 2 + + collection = arguments[0].dup + Array(arguments[1]).each do |item| + case collection + when Array, Hash, String + collection.reject! { |coll_item| (coll_item =~ %r{\b#{item}\b}) } + else + raise(TypeError, "delete_regex(): First argument must be an Array, Hash, or String. Given an argument of class #{collection.class}.") + end + end + collection + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb new file mode 100644 index 0000000..00bd252 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb @@ -0,0 +1,31 @@ +module Puppet::Parser::Functions + newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS +Returns a copy of input hash or array with all undefs deleted. + +*Examples:* + + $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + +Would return: {a => 'A', b => '', d => false} + + $array = delete_undef_values(['A','',undef,false]) + +Would return: ['A','',false] + + EOS + ) do |args| + + raise(Puppet::ParseError, "delete_undef_values(): Wrong number of arguments given (#{args.size})") if args.size < 1 + + unless args[0].is_a? Array or args[0].is_a? Hash + raise(Puppet::ParseError, "delete_undef_values(): expected an array or hash, got #{args[0]} type #{args[0].class} ") + end + result = args[0].dup + if result.is_a?(Hash) + result.delete_if {|key, val| val.equal? :undef} + elsif result.is_a?(Array) + result.delete :undef + end + result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_values.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_values.rb new file mode 100644 index 0000000..e799aef --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/delete_values.rb @@ -0,0 +1,23 @@ +module Puppet::Parser::Functions + newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given value from a hash. + +*Examples:* + + delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B') + +Would return: {'a'=>'A','c'=>'C','B'=>'D'} + + EOS + ) do |arguments| + + raise(Puppet::ParseError, "delete_values(): Wrong number of arguments given (#{arguments.size} of 2)") if arguments.size != 2 + + hash, item = arguments + + if not hash.is_a?(Hash) + raise(TypeError, "delete_values(): First argument must be a Hash. Given an argument of class #{hash.class}.") + end + hash.dup.delete_if { |key, val| item == val } + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/deprecation.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/deprecation.rb new file mode 100644 index 0000000..39d306a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/deprecation.rb @@ -0,0 +1,16 @@ +module Puppet::Parser::Functions + newfunction(:deprecation, :doc => <<-EOS + Function to print deprecation warnings (this is the 3.X version of it), The uniqueness key - can appear once. The msg is the message text including any positional information that is formatted by the user/caller of the method.). +EOS + ) do |arguments| + + raise(Puppet::ParseError, "deprecation: Wrong number of arguments given (#{arguments.size} for 2)") unless arguments.size == 2 + + key = arguments[0] + message = arguments[1] + + if ENV['STDLIB_LOG_DEPRECATIONS'] == "true" + warning("deprecation. #{key}. #{message}") + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/difference.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/difference.rb new file mode 100644 index 0000000..c9ac478 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/difference.rb @@ -0,0 +1,35 @@ +# +# difference.rb +# + +module Puppet::Parser::Functions + newfunction(:difference, :type => :rvalue, :doc => <<-EOS +This function returns the difference between two arrays. +The returned array is a copy of the original array, removing any items that +also appear in the second array. + +*Examples:* + + difference(["a","b","c"],["b","c","d"]) + +Would return: ["a"] + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "difference(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'difference(): Requires 2 arrays') + end + + result = first - second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/dig.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/dig.rb new file mode 100644 index 0000000..34fa701 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/dig.rb @@ -0,0 +1,16 @@ +# +# dig.rb +# + +module Puppet::Parser::Functions + newfunction(:dig, :type => :rvalue, :doc => <<-EOS + DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version. + EOS + ) do |arguments| + warning("dig() DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version.") + if ! Puppet::Parser::Functions.autoloader.loaded?(:dig44) + Puppet::Parser::Functions.autoloader.load(:dig44) + end + function_dig44(arguments) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/dig44.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/dig44.rb new file mode 100644 index 0000000..21c0a8c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/dig44.rb @@ -0,0 +1,70 @@ +# +# dig44.rb +# + +module Puppet::Parser::Functions + newfunction( + :dig44, + :type => :rvalue, + :arity => -2, + :doc => <<-eos +DEPRECATED: This function has been replaced in puppet 4.5.0. + +Looks up into a complex structure of arrays and hashes and returns a value +or the default value if nothing was found. + +Key can contain slashes to describe path components. The function will go down +the structure and try to extract the required value. + +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = dig44($data, ['a', 'b', '2'], 'not_found') +=> $value = 'b3' + +a -> first hash key +b -> second hash key +2 -> array index starting with 0 + +not_found -> (optional) will be returned if there is no value or the path +did not match. Defaults to nil. + +In addition to the required "key" argument, the function accepts a default +argument. It will be returned if no value was found or a path component is +missing. And the fourth argument can set a variable path separator. + eos + ) do |arguments| + # Two arguments are required + raise(Puppet::ParseError, "dig44(): Wrong number of arguments given (#{arguments.size} for at least 2)") if arguments.size < 2 + + data, path, default = *arguments + + unless data.is_a?(Hash) or data.is_a?(Array) + raise(Puppet::ParseError, "dig44(): first argument must be a hash or an array, given #{data.class.name}") + end + + unless path.is_a? Array + raise(Puppet::ParseError, "dig44(): second argument must be an array, given #{path.class.name}") + end + + value = path.reduce(data) do |structure, key| + if structure.is_a? Hash or structure.is_a? Array + if structure.is_a? Array + key = Integer key rescue break + end + break if structure[key].nil? or structure[key] == :undef + structure[key] + else + break + end + end + value.nil? ? default : value + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/dirname.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/dirname.rb new file mode 100644 index 0000000..40b300d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/dirname.rb @@ -0,0 +1,21 @@ +module Puppet::Parser::Functions + newfunction(:dirname, :type => :rvalue, :doc => <<-EOS + Returns the dirname of a path. + EOS + ) do |arguments| + + if arguments.size < 1 then + raise(Puppet::ParseError, "dirname(): No arguments given") + end + if arguments.size > 1 then + raise(Puppet::ParseError, "dirname(): Too many arguments given (#{arguments.size})") + end + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'dirname(): Requires string as argument') + end + + return File.dirname(arguments[0]) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb new file mode 100644 index 0000000..ccac899 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/dos2unix.rb @@ -0,0 +1,15 @@ +# Custom Puppet function to convert dos to unix format +module Puppet::Parser::Functions + newfunction(:dos2unix, :type => :rvalue, :arity => 1, :doc => <<-EOS + Returns the Unix version of the given string. + Takes a single string argument. + EOS + ) do |arguments| + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'dos2unix(): Requires string as argument') + end + + arguments[0].gsub(/\r\n/, "\n") + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/downcase.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/downcase.rb new file mode 100644 index 0000000..ab04f8a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/downcase.rb @@ -0,0 +1,31 @@ +# +# downcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:downcase, :type => :rvalue, :doc => <<-EOS +Converts the case of a string or all strings in an array to lower case. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "downcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'downcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.downcase : i } + else + result = value.downcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/empty.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/empty.rb new file mode 100644 index 0000000..4f77ad3 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/empty.rb @@ -0,0 +1,29 @@ +# +# empty.rb +# + +module Puppet::Parser::Functions + newfunction(:empty, :type => :rvalue, :doc => <<-EOS +Returns true if the variable is empty. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "empty(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(String) || value.is_a?(Numeric) + raise(Puppet::ParseError, 'empty(): Requires either array, hash, string or integer to work with') + end + + if value.is_a?(Numeric) + return false + else + result = value.empty? + + return result + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb new file mode 100644 index 0000000..1f8a454 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/enclose_ipv6.rb @@ -0,0 +1,42 @@ +# +# enclose_ipv6.rb +# + +module Puppet::Parser::Functions + newfunction(:enclose_ipv6, :type => :rvalue, :doc => <<-EOS +Takes an array of ip addresses and encloses the ipv6 addresses with square brackets. + EOS + ) do |arguments| + + require 'ipaddr' + + rescuable_exceptions = [ ArgumentError ] + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + if (arguments.size != 1) then + raise(Puppet::ParseError, "enclose_ipv6(): Wrong number of arguments given #{arguments.size} for 1") + end + unless arguments[0].is_a?(String) or arguments[0].is_a?(Array) then + raise(Puppet::ParseError, "enclose_ipv6(): Wrong argument type given #{arguments[0].class} expected String or Array") + end + + input = [arguments[0]].flatten.compact + result = [] + + input.each do |val| + unless val == '*' + begin + ip = IPAddr.new(val) + rescue *rescuable_exceptions + raise(Puppet::ParseError, "enclose_ipv6(): Wrong argument given #{val} is not an ip address.") + end + val = "[#{ip.to_s}]" if ip.ipv6? + end + result << val + end + + return result.uniq + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb new file mode 100644 index 0000000..17942b8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb @@ -0,0 +1,47 @@ +# +# ensure_packages.rb +# + +module Puppet::Parser::Functions + newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS +Takes a list of packages and only installs them if they don't already exist. +It optionally takes a hash as a second parameter that will be passed as the +third argument to the ensure_resource() function. + EOS + ) do |arguments| + + if arguments.size > 2 or arguments.size == 0 + raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments given (#{arguments.size} for 1 or 2)") + elsif arguments.size == 2 and !arguments[1].is_a?(Hash) + raise(Puppet::ParseError, 'ensure_packages(): Requires second argument to be a Hash') + end + + if arguments[0].is_a?(Hash) + if arguments[1] + defaults = { 'ensure' => 'present' }.merge(arguments[1]) + else + defaults = { 'ensure' => 'present' } + end + + Puppet::Parser::Functions.function(:ensure_resources) + function_ensure_resources(['package', arguments[0].dup, defaults ]) + else + packages = Array(arguments[0]) + + if arguments[1] + defaults = { 'ensure' => 'present' }.merge(arguments[1]) + else + defaults = { 'ensure' => 'present' } + end + + Puppet::Parser::Functions.function(:ensure_resource) + packages.each { |package_name| + if !findresource("Package[#{package_name}]") + function_ensure_resource(['package', package_name, defaults ]) + end + } + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb new file mode 100644 index 0000000..1ba6a44 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb @@ -0,0 +1,46 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:ensure_resource, + :type => :statement, + :doc => <<-'ENDOFDOC' +Takes a resource type, title, and a list of attributes that describe a +resource. + + user { 'dan': + ensure => present, + } + +This example only creates the resource if it does not already exist: + + ensure_resource('user', 'dan', {'ensure' => 'present' }) + +If the resource already exists but does not match the specified parameters, +this function will attempt to recreate the resource leading to a duplicate +resource definition error. + +An array of resources can also be passed in and each will be created with +the type and parameters specified if it doesn't already exist. + + ensure_resource('user', ['dan','alex'], {'ensure' => 'present'}) + +ENDOFDOC +) do |vals| + type, title, params = vals + raise(ArgumentError, 'Must specify a type') unless type + raise(ArgumentError, 'Must specify a title') unless title + params ||= {} + + items = [title].flatten + + items.each do |item| + Puppet::Parser::Functions.function(:defined_with_params) + if function_defined_with_params(["#{type}[#{item}]", params]) + Puppet.debug("Resource #{type}[#{item}] with params #{params} not created because it already exists") + else + Puppet.debug("Create new resource #{type}[#{item}] with params #{params}") + Puppet::Parser::Functions.function(:create_resources) + function_create_resources([type.capitalize, { item => params }]) + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb new file mode 100644 index 0000000..b3c51e6 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/ensure_resources.rb @@ -0,0 +1,54 @@ +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:ensure_resources, + :type => :statement, + :doc => <<-'ENDOFDOC' +Takes a resource type, title (only hash), and a list of attributes that describe a +resource. + + user { 'dan': + gid => 'mygroup', + ensure => present, + } + +An hash of resources should be passed in and each will be created with +the type and parameters specified if it doesn't already exist. + + ensure_resources('user', {'dan' => { gid => 'mygroup', uid => '600' } , 'alex' => { gid => 'mygroup' }}, {'ensure' => 'present'}) + +From Hiera Backend: + +userlist: + dan: + gid: 'mygroup' + uid: '600' + alex: + gid: 'mygroup' + +Call: +ensure_resources('user', hiera_hash('userlist'), {'ensure' => 'present'}) + +ENDOFDOC +) do |vals| + type, title, params = vals + raise(ArgumentError, 'Must specify a type') unless type + raise(ArgumentError, 'Must specify a title') unless title + params ||= {} + + if title.is_a?(Hash) + resource_hash = title.dup + resources = resource_hash.keys + + Puppet::Parser::Functions.function(:ensure_resource) + resources.each { |resource_name| + if resource_hash[resource_name] + params_merged = params.merge(resource_hash[resource_name]) + else + params_merged = params + end + function_ensure_resource([ type, resource_name, params_merged ]) + } + else + raise(Puppet::ParseError, 'ensure_resources(): Requires second argument to be a Hash') + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/flatten.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/flatten.rb new file mode 100644 index 0000000..4401bdb --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/flatten.rb @@ -0,0 +1,32 @@ +# +# flatten.rb +# + +module Puppet::Parser::Functions + newfunction(:flatten, :type => :rvalue, :doc => <<-EOS +This function flattens any deeply nested arrays and returns a single flat array +as a result. + +*Examples:* + + flatten(['a', ['b', ['c']]]) + +Would return: ['a','b','c'] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "flatten(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'flatten(): Requires array to work with') + end + + result = array.flatten + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/floor.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/floor.rb new file mode 100644 index 0000000..9e4b504 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/floor.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions + newfunction(:floor, :type => :rvalue, :doc => <<-EOS + Returns the largest integer less or equal to the argument. + Takes a single numeric value as an argument. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "floor(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + begin + arg = Float(arguments[0]) + rescue TypeError, ArgumentError => e + raise(Puppet::ParseError, "floor(): Wrong argument type given (#{arguments[0]} for Numeric)") + end + + raise(Puppet::ParseError, "floor(): Wrong argument type given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false + + arg.floor + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb new file mode 100644 index 0000000..2bb1287 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb @@ -0,0 +1,34 @@ +Puppet::Parser::Functions::newfunction( + :fqdn_rand_string, + :arity => -2, + :type => :rvalue, + :doc => "Usage: `fqdn_rand_string(LENGTH, [CHARSET], [SEED])`. LENGTH is + required and must be a positive integer. CHARSET is optional and may be + `undef` or a string. SEED is optional and may be any number or string. + + Generates a random string LENGTH characters long using the character set + provided by CHARSET, combining the `$fqdn` fact and the value of SEED for + repeatable randomness. (That is, each node will get a different random + string from this function, but a given node's result will be the same every + time unless its hostname changes.) Adding a SEED can be useful if you need + more than one unrelated string. CHARSET will default to alphanumeric if + `undef` or an empty string.") do |args| + raise(ArgumentError, "fqdn_rand_string(): wrong number of arguments (0 for 1)") if args.size == 0 + Puppet::Parser::Functions.function('is_integer') + raise(ArgumentError, "fqdn_rand_string(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0 + raise(ArgumentError, "fqdn_rand_string(): second argument must be undef or a string") unless args[1].nil? or args[1].is_a? String + + Puppet::Parser::Functions.function('fqdn_rand') + + length = args.shift.to_i + charset = args.shift.to_s.chars.to_a + + charset = (0..9).map { |i| i.to_s } + ('A'..'Z').to_a + ('a'..'z').to_a if charset.empty? + + rand_string = '' + for current in 1..length + rand_string << charset[function_fqdn_rand([charset.size, (args + [current.to_s]).join(':')]).to_i] + end + + rand_string +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb new file mode 100644 index 0000000..05bdcc7 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb @@ -0,0 +1,61 @@ +# +# fqdn_rotate.rb +# + +Puppet::Parser::Functions.newfunction( + :fqdn_rotate, + :type => :rvalue, + :doc => "Usage: `fqdn_rotate(VALUE, [SEED])`. VALUE is required and + must be an array or a string. SEED is optional and may be any number + or string. + + Rotates VALUE a random number of times, combining the `$fqdn` fact and + the value of SEED for repeatable randomness. (That is, each node will + get a different random rotation from this function, but a given node's + result will be the same every time unless its hostname changes.) Adding + a SEED can be useful if you need more than one unrelated rotation.") do |args| + + raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments given (#{args.size} for 1)") if args.size < 1 + + value = args.shift + require 'digest/md5' + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'fqdn_rotate(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # Check whether it makes sense to rotate ... + return result if result.size <= 1 + + # We turn any string value into an array to be able to rotate ... + result = string ? result.split('') : result + + elements = result.size + + seed = Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex + # deterministic_rand() was added in Puppet 3.2.0; reimplement if necessary + if Puppet::Util.respond_to?(:deterministic_rand) + offset = Puppet::Util.deterministic_rand(seed, elements).to_i + else + if defined?(Random) == 'constant' && Random.class == Class + offset = Random.new(seed).rand(elements) + else + old_seed = srand(seed) + offset = rand(elements) + srand(old_seed) + end + end + offset.times { + result.push result.shift + } + + result = string ? result.join : result + + return result +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb new file mode 100644 index 0000000..30205d0 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb @@ -0,0 +1,92 @@ +require 'digest/sha1' + +module Puppet::Parser::Functions + newfunction(:fqdn_uuid, :type => :rvalue, :doc => <<-END) do |args| + + Creates a UUID based on a given string, assumed to be the FQDN + + For example, to generate a UUID based on the FQDN of a system: + + Usage: + + $uuid = fqdn_uuid($::fqdn) + + The generated UUID will be the same for the given hostname + + The resulting UUID is returned on the form: + + 1d839dea-5e10-5243-88eb-e66815bd7d5c + + (u.e. without any curly braces.) + + The generated UUID is a version 5 UUID with the V5 DNS namespace: + + 6ba7b810-9dad-11d1-80b4-00c04fd430c8 + + This only supports a the V5 SHA-1 hash, using the DNS namespace. + + Please consult http://www.ietf.org/rfc/rfc4122.txt for the details on + UUID generation and example implementation. + + No verification is present at the moment as whether the domain name given + is in fact a correct fully-qualified domain name. Therefore any arbitrary + string and/or alpha-numeric value can subside for a domain name. + EOS + + END + + if args.length == 0 + raise(ArgumentError, "fqdn_uuid: No arguments given") + elsif args.length == 1 + fqdn = args[0] + else + raise(ArgumentError, "fqdn_uuid: Too many arguments given (#{args.length})") + end + + # Code lovingly taken from + # https://github.com/puppetlabs/marionette-collective/blob/master/lib/mcollective/ssl.rb + + # This is the UUID version 5 type DNS name space which is as follows: + # + # 6ba7b810-9dad-11d1-80b4-00c04fd430c8 + # + uuid_name_space_dns = [0x6b, + 0xa7, + 0xb8, + 0x10, + 0x9d, + 0xad, + 0x11, + 0xd1, + 0x80, + 0xb4, + 0x00, + 0xc0, + 0x4f, + 0xd4, + 0x30, + 0xc8 + ].map {|b| b.chr}.join + + sha1 = Digest::SHA1.new + sha1.update(uuid_name_space_dns) + sha1.update(fqdn) + + # first 16 bytes.. + bytes = sha1.digest[0, 16].bytes.to_a + + # version 5 adjustments + bytes[6] &= 0x0f + bytes[6] |= 0x50 + + # variant is DCE 1.1 + bytes[8] &= 0x3f + bytes[8] |= 0x80 + + bytes = [4, 2, 2, 2, 6].collect do |i| + bytes.slice!(0, i).pack('C*').unpack('H*') + end + + bytes.join('-') + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb new file mode 100644 index 0000000..1421b91 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb @@ -0,0 +1,17 @@ +module Puppet::Parser::Functions + newfunction(:get_module_path, :type =>:rvalue, :doc => <<-EOT + Returns the absolute path of the specified module for the current + environment. + + Example: + $module_path = get_module_path('stdlib') + EOT + ) do |args| + raise(Puppet::ParseError, "get_module_path(): Wrong number of arguments, expects one") unless args.size == 1 + if module_path = Puppet::Module.find(args[0], compiler.environment.to_s) + module_path.path + else + raise(Puppet::ParseError, "Could not find module #{args[0]} in environment #{compiler.environment}") + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/getparam.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/getparam.rb new file mode 100644 index 0000000..0a5cbe0 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/getparam.rb @@ -0,0 +1,35 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:getparam, + :type => :rvalue, + :doc => <<-'ENDOFDOC' +Takes a resource reference and name of the parameter and +returns value of resource's parameter. + +*Examples:* + + define example_resource($param) { + } + + example_resource { "example_resource_instance": + param => "param_value" + } + + getparam(Example_resource["example_resource_instance"], "param") + +Would return: param_value +ENDOFDOC +) do |vals| + reference, param = vals + raise(ArgumentError, 'Must specify a reference') unless reference + raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String + + return '' if param.empty? + + if resource = findresource(reference.to_s) + return resource[param] unless resource[param].nil? + end + + return '' +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/getvar.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/getvar.rb new file mode 100644 index 0000000..3af8d48 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/getvar.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:getvar, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Lookup a variable in a remote namespace. + + For example: + + $foo = getvar('site::data::foo') + # Equivalent to $foo = $site::data::foo + + This is useful if the namespace itself is stored in a string: + + $datalocation = 'site::data' + $bar = getvar("${datalocation}::bar") + # Equivalent to $bar = $site::data::bar + ENDHEREDOC + + unless args.length == 1 + raise Puppet::ParseError, ("getvar(): wrong number of arguments (#{args.length}; must be 1)") + end + + begin + result = nil + catch(:undefined_variable) do + result = self.lookupvar("#{args[0]}") + end + + # avoid relying on incosistent behaviour around ruby return values from catch + result + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/grep.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/grep.rb new file mode 100644 index 0000000..c611a7e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/grep.rb @@ -0,0 +1,32 @@ +# +# grep.rb +# + +module Puppet::Parser::Functions + newfunction(:grep, :type => :rvalue, :doc => <<-EOS +This function searches through an array and returns any elements that match +the provided regular expression. + +*Examples:* + + grep(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + + ['aaa','aaaddd'] + EOS + ) do |arguments| + + if (arguments.size != 2) then + raise(Puppet::ParseError, "grep(): Wrong number of arguments given #{arguments.size} for 2") + end + + a = arguments[0] + pattern = Regexp.new(arguments[1]) + + a.grep(pattern) + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb new file mode 100644 index 0000000..f6cb74b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb @@ -0,0 +1,70 @@ +# +# has_interface_with +# + +module Puppet::Parser::Functions + newfunction(:has_interface_with, :type => :rvalue, :doc => <<-EOS +Returns boolean based on kind and value: + * macaddress + * netmask + * ipaddress + * network + +has_interface_with("macaddress", "x:x:x:x:x:x") +has_interface_with("ipaddress", "127.0.0.1") => true +etc. + +If no "kind" is given, then the presence of the interface is checked: +has_interface_with("lo") => true + EOS + ) do |args| + + raise(Puppet::ParseError, "has_interface_with(): Wrong number of arguments given (#{args.size} for 1 or 2)") if args.size < 1 or args.size > 2 + + interfaces = lookupvar('interfaces') + + # If we do not have any interfaces, then there are no requested attributes + return false if (interfaces == :undefined || interfaces.nil?) + + interfaces = interfaces.split(',') + + if args.size == 1 + return interfaces.member?(args[0]) + end + + kind, value = args + + # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable + # https://tickets.puppetlabs.com/browse/PUP-3597 + factval = nil + begin + catch :undefined_variable do + factval = lookupvar(kind) + end + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + if factval == value + return true + end + + result = false + interfaces.each do |iface| + iface.downcase! + factval = nil + begin + # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable + # https://tickets.puppetlabs.com/browse/PUP-3597 + catch :undefined_variable do + factval = lookupvar("#{kind}_#{iface}") + end + rescue Puppet::ParseError # Eat the exception if strict_variables = true is set + end + if value == factval + result = true + break + end + end + + result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb new file mode 100644 index 0000000..a0071c8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb @@ -0,0 +1,24 @@ +# +# has_ip_address +# + +module Puppet::Parser::Functions + newfunction(:has_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the client has the requested IP address on some interface. + +This function iterates through the 'interfaces' fact and checks the +'ipaddress_IFACE' facts, performing a simple string comparison. + EOS + ) do |args| + + raise(Puppet::ParseError, "has_ip_address(): Wrong number of arguments given (#{args.size} for 1)") if args.size != 1 + + Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ + unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + + function_has_interface_with(['ipaddress', args[0]]) + + end +end + +# vim:sts=2 sw=2 diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb new file mode 100644 index 0000000..b02c0c0 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb @@ -0,0 +1,24 @@ +# +# has_ip_network +# + +module Puppet::Parser::Functions + newfunction(:has_ip_network, :type => :rvalue, :doc => <<-EOS +Returns true if the client has an IP address within the requested network. + +This function iterates through the 'interfaces' fact and checks the +'network_IFACE' facts, performing a simple string comparision. + EOS + ) do |args| + + raise(Puppet::ParseError, "has_ip_network(): Wrong number of arguments given (#{args.size} for 1)") if args.size != 1 + + Puppet::Parser::Functions.autoloader.load(:has_interface_with) \ + unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with) + + function_has_interface_with(['network', args[0]]) + + end +end + +# vim:sts=2 sw=2 diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/has_key.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_key.rb new file mode 100644 index 0000000..4657cc2 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/has_key.rb @@ -0,0 +1,28 @@ +module Puppet::Parser::Functions + + newfunction(:has_key, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Determine if a hash has a certain key value. + + Example: + + $my_hash = {'key_one' => 'value_one'} + if has_key($my_hash, 'key_two') { + notice('we will not reach here') + } + if has_key($my_hash, 'key_one') { + notice('this will be printed') + } + + ENDHEREDOC + + unless args.length == 2 + raise Puppet::ParseError, ("has_key(): wrong number of arguments (#{args.length}; must be 2)") + end + unless args[0].is_a?(Hash) + raise Puppet::ParseError, "has_key(): expects the first argument to be a hash, got #{args[0].inspect} which is of type #{args[0].class}" + end + args[0].has_key?(args[1]) + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/hash.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/hash.rb new file mode 100644 index 0000000..22763f3 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/hash.rb @@ -0,0 +1,39 @@ +# +# hash.rb +# + +module Puppet::Parser::Functions + newfunction(:hash, :type => :rvalue, :doc => <<-EOS +This function converts an array into a hash. + +*Examples:* + + hash(['a',1,'b',2,'c',3]) + +Would return: {'a'=>1,'b'=>2,'c'=>3} + EOS + ) do |arguments| + + raise(Puppet::ParseError, "hash(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'hash(): Requires array to work with') + end + + result = {} + + begin + # This is to make it compatible with older version of Ruby ... + array = array.flatten + result = Hash[*array] + rescue StandardError + raise(Puppet::ParseError, 'hash(): Unable to compute hash from array given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/intersection.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/intersection.rb new file mode 100644 index 0000000..8a438f4 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/intersection.rb @@ -0,0 +1,33 @@ +# +# intersection.rb +# + +module Puppet::Parser::Functions + newfunction(:intersection, :type => :rvalue, :doc => <<-EOS +This function returns an array of the intersection of two. + +*Examples:* + + intersection(["a","b","c"],["b","c","d"]) # returns ["b","c"] + intersection(["a","b","c"],[1,2,3,4]) # returns [] (true, when evaluated as a Boolean) + + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "intersection(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'intersection(): Requires 2 arrays') + end + + result = first & second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb new file mode 100644 index 0000000..e64777f --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + newfunction(:is_absolute_path, :type => :rvalue, :arity => 1, :doc => <<-'ENDHEREDOC') do |args| + Returns boolean true if the string represents an absolute path in the filesystem. This function works + for windows and unix style paths. + + The following values will return true: + + $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' + is_absolute_path($my_path) + $my_path2 = '/var/lib/puppet' + is_absolute_path($my_path2) + $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet'] + is_absolute_path($my_path3) + $my_path4 = ['/var/lib/puppet'] + is_absolute_path($my_path4) + + The following values will return false: + + is_absolute_path(true) + is_absolute_path('../var/lib/puppet') + is_absolute_path('var/lib/puppet') + $undefined = undef + is_absolute_path($undefined) + + ENDHEREDOC + function_deprecation([:is_absolute_path, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_path. There is further documentation for validate_legacy function in the README.']) + require 'puppet/util' + + path = args[0] + # This logic was borrowed from + # [lib/puppet/file_serving/base.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/file_serving/base.rb) + # Puppet 2.7 and beyond will have Puppet::Util.absolute_path? Fall back to a back-ported implementation otherwise. + if Puppet::Util.respond_to?(:absolute_path?) then + value = (Puppet::Util.absolute_path?(path, :posix) or Puppet::Util.absolute_path?(path, :windows)) + else + # This code back-ported from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path? + # Determine in a platform-specific way whether a path is absolute. This + # defaults to the local platform if none is specified. + # Escape once for the string literal, and once for the regex. + slash = '[\\\\/]' + name = '[^\\\\/]+' + regexes = { + :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, + :posix => %r!^/! + } + value = (!!(path =~ regexes[:posix])) || (!!(path =~ regexes[:windows])) + end + value + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_array.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_array.rb new file mode 100644 index 0000000..a33afe4 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_array.rb @@ -0,0 +1,21 @@ +# +# is_array.rb +# + +module Puppet::Parser::Functions + newfunction(:is_array, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an array. + EOS + ) do |arguments| + + function_deprecation([:is_array, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_array(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + type = arguments[0] + + result = type.is_a?(Array) + + return result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_bool.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_bool.rb new file mode 100644 index 0000000..2ebe430 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_bool.rb @@ -0,0 +1,21 @@ +# +# is_bool.rb +# + +module Puppet::Parser::Functions + newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a boolean. + EOS + ) do |arguments| + + function_deprecation([:is_bool, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + type = arguments[0] + + result = type.is_a?(TrueClass) || type.is_a?(FalseClass) + + return result + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb new file mode 100644 index 0000000..247db3b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb @@ -0,0 +1,53 @@ +# +# is_domain_name.rb +# + +module Puppet::Parser::Functions + newfunction(:is_domain_name, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a syntactically correct domain name. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_domain_name(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Only allow string types + return false unless arguments[0].is_a?(String) + + domain = arguments[0].dup + + # Limits (rfc1035, 3.1) + domain_max_length=255 + label_min_length=1 + label_max_length=63 + + # Allow ".", it is the top level domain + return true if domain == '.' + + # Remove the final dot, if present. + domain.chomp!('.') + + # Check the whole domain + return false if domain.empty? + return false if domain.length > domain_max_length + + # The top level domain must be alphabetic if there are multiple labels. + # See rfc1123, 2.1 + return false if domain.include? '.' and not /\.[A-Za-z]+$/.match(domain) + + # Check each label in the domain + labels = domain.split('.') + vlabels = labels.each do |label| + break if label.length < label_min_length + break if label.length > label_max_length + break if label[-1..-1] == '-' + break if label[0..0] == '-' + break unless /^[a-z\d-]+$/i.match(label) + end + return vlabels == labels + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb new file mode 100644 index 0000000..bcd7921 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_email_address.rb @@ -0,0 +1,20 @@ +# +# is_email_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_email_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid email address. + EOS + ) do |arguments| + if arguments.size != 1 + raise(Puppet::ParseError, "is_email_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Taken from http://emailregex.com/ (simpler regex) + valid_email_regex = %r{\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z} + return (arguments[0] =~ valid_email_regex) == 0 + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_float.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_float.rb new file mode 100644 index 0000000..aa84a2b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_float.rb @@ -0,0 +1,31 @@ +# +# is_float.rb +# + +module Puppet::Parser::Functions + newfunction(:is_float, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a float. + EOS + ) do |arguments| + + function_deprecation([:is_float, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Float. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_float(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Only allow Numeric or String types + return false unless value.is_a?(Numeric) or value.is_a?(String) + + if value != value.to_f.to_s and !value.is_a? Float then + return false + else + return true + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb new file mode 100644 index 0000000..50cd5e1 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb @@ -0,0 +1,25 @@ +# +# is_function_available.rb +# + +module Puppet::Parser::Functions + newfunction(:is_function_available, :type => :rvalue, :doc => <<-EOS +This function accepts a string as an argument, determines whether the +Puppet runtime has access to a function by that name. It returns a +true if the function exists, false if not. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_function_available?(): Wrong number of arguments given #{arguments.size} for 1") + end + + # Only allow String types + return false unless arguments[0].is_a?(String) + + function = Puppet::Parser::Functions.function(arguments[0].to_sym) + function.is_a?(String) and not function.empty? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_hash.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_hash.rb new file mode 100644 index 0000000..3162f7d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_hash.rb @@ -0,0 +1,21 @@ +# +# is_hash.rb +# + +module Puppet::Parser::Functions + newfunction(:is_hash, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a hash. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "is_hash(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + type = arguments[0] + + result = type.is_a?(Hash) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_integer.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_integer.rb new file mode 100644 index 0000000..8965b15 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_integer.rb @@ -0,0 +1,46 @@ +# +# is_integer.rb +# + +module Puppet::Parser::Functions + newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is an Integer or +a decimal (base 10) integer in String form. The string may +start with a '-' (minus). A value of '0' is allowed, but a leading '0' digit may not +be followed by other digits as this indicates that the value is octal (base 8). + +If given any other argument `false` is returned. + EOS + ) do |arguments| + + function_deprecation([:is_integer, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_integer(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Regex is taken from the lexer of puppet + # puppet/pops/parser/lexer.rb but modified to match also + # negative values and disallow numbers prefixed with multiple + # 0's + # + # TODO these parameter should be a constant but I'm not sure + # if there is no risk to declare it inside of the module + # Puppet::Parser::Functions + + # Integer numbers like + # -1234568981273 + # 47291 + numeric = %r{^-?(?:(?:[1-9]\d*)|0)$} + + if value.is_a? Integer or (value.is_a? String and value.match numeric) + return true + else + return false + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb new file mode 100644 index 0000000..79ddb98 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb @@ -0,0 +1,33 @@ +# +# is_ip_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ip_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IP address. + EOS + ) do |arguments| + + require 'ipaddr' + + function_deprecation([:is_ip_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_address. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ip_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + if ip.ipv4? or ip.ipv6? then + return true + else + return false + end + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb new file mode 100644 index 0000000..91869b6 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv4_address.rb @@ -0,0 +1,29 @@ +# +# is_ipv4_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ipv4_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IPv4 address. + EOS + ) do |arguments| + + require 'ipaddr' + + function_deprecation([:is_ipv4_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ipv4_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + return ip.ipv4? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb new file mode 100644 index 0000000..4d96202 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_ipv6_address.rb @@ -0,0 +1,29 @@ +# +# is_ipv6_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_ipv6_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid IPv6 address. + EOS + ) do |arguments| + + function_deprecation([:is_ipv6_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6. There is further documentation for validate_legacy function in the README.']) + + require 'ipaddr' + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_ipv6_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + begin + ip = IPAddr.new(arguments[0]) + rescue ArgumentError + return false + end + + return ip.ipv6? + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb new file mode 100644 index 0000000..5993ed2 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb @@ -0,0 +1,26 @@ +# +# is_mac_address.rb +# + +module Puppet::Parser::Functions + newfunction(:is_mac_address, :type => :rvalue, :doc => <<-EOS +Returns true if the string passed to this function is a valid mac address. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_mac_address(): Wrong number of arguments given #{arguments.size} for 1") + end + + mac = arguments[0] + + if /^[a-f0-9]{1,2}(:[a-f0-9]{1,2}){5}$/i.match(mac) then + return true + else + return false + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb new file mode 100644 index 0000000..7800edd --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb @@ -0,0 +1,74 @@ +# +# is_numeric.rb +# + +module Puppet::Parser::Functions + newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS +Returns true if the given argument is a Numeric (Integer or Float), +or a String containing either a valid integer in decimal base 10 form, or +a valid floating point string representation. + +The function recognizes only decimal (base 10) integers and float but not +integers in hex (base 16) or octal (base 8) form. + +The string representation may start with a '-' (minus). If a decimal '.' is used, +it must be followed by at least one digit. + +Valid examples: + + 77435 + 10e-12 + -8475 + 0.2343 + -23.561e3 + EOS + ) do |arguments| + + function_deprecation([:is_numeric, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.']) + + if (arguments.size != 1) then + raise(Puppet::ParseError, "is_numeric(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + # Regex is taken from the lexer of puppet + # puppet/pops/parser/lexer.rb but modified to match also + # negative values and disallow invalid octal numbers or + # numbers prefixed with multiple 0's (except in hex numbers) + # + # TODO these parameters should be constants but I'm not sure + # if there is no risk to declare them inside of the module + # Puppet::Parser::Functions + + # TODO decide if this should be used + # HEX numbers like + # 0xaa230F + # 0X1234009C + # 0x0012 + # -12FcD + #numeric_hex = %r{^-?0[xX][0-9A-Fa-f]+$} + + # TODO decide if this should be used + # OCTAL numbers like + # 01234567 + # -045372 + #numeric_oct = %r{^-?0[1-7][0-7]*$} + + # Integer/Float numbers like + # -0.1234568981273 + # 47291 + # 42.12345e-12 + numeric = %r{^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$} + + if value.is_a? Numeric or (value.is_a? String and ( + value.match(numeric) #or + # value.match(numeric_hex) or + # value.match(numeric_oct) + )) + return true + else + return false + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/is_string.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_string.rb new file mode 100644 index 0000000..0ed6aec --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/is_string.rb @@ -0,0 +1,28 @@ +# +# is_string.rb +# + +module Puppet::Parser::Functions + newfunction(:is_string, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a string. + EOS + ) do |arguments| + + function_deprecation([:is_string, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.']) + + raise(Puppet::ParseError, "is_string(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + type = arguments[0] + + # when called through the v4 API shim, undef gets translated to nil + result = type.is_a?(String) || type.nil? + + if result and (type == type.to_f.to_s or type == type.to_i.to_s) then + return false + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/join.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/join.rb new file mode 100644 index 0000000..1c17997 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/join.rb @@ -0,0 +1,40 @@ +# +# join.rb +# + +module Puppet::Parser::Functions + newfunction(:join, :type => :rvalue, :doc => <<-EOS +This function joins an array into a string using a separator. + +*Examples:* + + join(['a','b','c'], ",") + +Would result in: "a,b,c" + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "join(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'join(): Requires array to work with') + end + + suffix = arguments[1] if arguments[1] + + if suffix + unless suffix.is_a?(String) + raise(Puppet::ParseError, 'join(): Requires string to work with') + end + end + + result = suffix ? array.join(suffix) : array.join + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb new file mode 100644 index 0000000..d8966ba --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb @@ -0,0 +1,53 @@ +# +# join.rb +# + +module Puppet::Parser::Functions + newfunction(:join_keys_to_values, :type => :rvalue, :doc => <<-EOS +This function joins each key of a hash to that key's corresponding value with a +separator. Keys are cast to strings. If values are arrays, multiple keys +are added for each element. The return value is an array in +which each element is one joined key/value pair. + +*Examples:* + + join_keys_to_values({'a'=>1,'b'=>2}, " is ") + +Would result in: ["a is 1","b is 2"] + + join_keys_to_values({'a'=>1,'b'=>[2,3]}, " is ") + +Would result in: ["a is 1","b is 2","b is 3"] + EOS + ) do |arguments| + + # Validate the number of arguments. + if arguments.size != 2 + raise(Puppet::ParseError, "join_keys_to_values(): Takes exactly two arguments, but #{arguments.size} given.") + end + + # Validate the first argument. + hash = arguments[0] + if not hash.is_a?(Hash) + raise(TypeError, "join_keys_to_values(): The first argument must be a hash, but a #{hash.class} was given.") + end + + # Validate the second argument. + separator = arguments[1] + if not separator.is_a?(String) + raise(TypeError, "join_keys_to_values(): The second argument must be a string, but a #{separator.class} was given.") + end + + # Join the keys to their values. + hash.map do |k,v| + if v.is_a?(Array) + v.map { |va| String(k) + separator + String(va) } + else + String(k) + separator + String(v) + end + end.flatten + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/keys.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/keys.rb new file mode 100644 index 0000000..199e319 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/keys.rb @@ -0,0 +1,25 @@ +# +# keys.rb +# + +module Puppet::Parser::Functions + newfunction(:keys, :type => :rvalue, :doc => <<-EOS +Returns the keys of a hash as an array. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "keys(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + hash = arguments[0] + + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'keys(): Requires hash to work with') + end + + result = hash.keys + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb new file mode 100644 index 0000000..c9b8488 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb @@ -0,0 +1,24 @@ +module Puppet::Parser::Functions + newfunction(:load_module_metadata, :type => :rvalue, :doc => <<-EOT + EOT + ) do |args| + raise(Puppet::ParseError, "load_module_metadata(): Wrong number of arguments, expects one or two") unless [1,2].include?(args.size) + mod = args[0] + allow_empty_metadata = args[1] + module_path = function_get_module_path([mod]) + metadata_json = File.join(module_path, 'metadata.json') + + metadata_exists = File.exists?(metadata_json) + if metadata_exists + metadata = PSON.load(File.read(metadata_json)) + else + if allow_empty_metadata + metadata = {} + else + raise(Puppet::ParseError, "load_module_metadata(): No metadata.json file for module #{mod}") + end + end + + return metadata + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/loadjson.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/loadjson.rb new file mode 100644 index 0000000..3a3372b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/loadjson.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:loadjson, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| +Load a JSON file containing an array, string, or hash, and return the data +in the corresponding native data type. +The second parameter is the default value. It will be returned if the file +was not found or could not be parsed. + +For example: + + $myhash = loadjson('/etc/puppet/data/myhash.json') + $myhash = loadjson('no-file.json', {'default' => 'value'}) + ENDHEREDOC + + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless args.length >= 1 + + if File.exists?(args[0]) + begin + content = File.read(args[0]) + PSON::load(content) || args[1] + rescue Exception => e + if args[1] + args[1] + else + raise e + end + end + else + warning("Can't load '#{args[0]}' File does not exist!") + args[1] + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb new file mode 100644 index 0000000..9696362 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:loadyaml, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| +Load a YAML file containing an array, string, or hash, and return the data +in the corresponding native data type. +The second parameter is the default value. It will be returned if the file +was not found or could not be parsed. + +For example: + + $myhash = loadyaml('/etc/puppet/data/myhash.yaml') + $myhash = loadyaml('no-file.yaml', {'default' => 'value'}) + ENDHEREDOC + + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless args.length >= 1 + require 'yaml' + + if File.exists?(args[0]) + begin + YAML::load_file(args[0]) || args[1] + rescue Exception => e + if args[1] + args[1] + else + raise e + end + end + else + warning("Can't load '#{args[0]}' File does not exist!") + args[1] + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/lstrip.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/lstrip.rb new file mode 100644 index 0000000..9a9ee29 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/lstrip.rb @@ -0,0 +1,30 @@ +# +# lstrip.rb +# + +module Puppet::Parser::Functions + newfunction(:lstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the left of a string. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "lstrip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'lstrip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.lstrip : i } + else + result = value.lstrip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/max.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/max.rb new file mode 100644 index 0000000..758c42c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/max.rb @@ -0,0 +1,20 @@ +module Puppet::Parser::Functions + newfunction(:max, :type => :rvalue, :doc => <<-EOS + Returns the highest value of all arguments. + Requires at least one argument. + EOS + ) do |args| + + raise(Puppet::ParseError, "max(): Wrong number of arguments need at least one") if args.size == 0 + + # Sometimes we get numbers as numerics and sometimes as strings. + # We try to compare them as numbers when possible + return args.max do |a,b| + if a.to_s =~ /\A-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then + a.to_f <=> b.to_f + else + a.to_s <=> b.to_s + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/member.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/member.rb new file mode 100644 index 0000000..fb93452 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/member.rb @@ -0,0 +1,60 @@ +# +# member.rb +# + +# TODO(Krzysztof Wilczynski): We need to add support for regular expression ... +# TODO(Krzysztof Wilczynski): Support for strings and hashes too ... + +module Puppet::Parser::Functions + newfunction(:member, :type => :rvalue, :doc => <<-EOS +This function determines if a variable is a member of an array. +The variable can be a string, fixnum, or array. + +*Examples:* + + member(['a','b'], 'b') + +Would return: true + + member(['a', 'b', 'c'], ['a', 'b']) + +would return: true + + member(['a','b'], 'c') + +Would return: false + + member(['a', 'b', 'c'], ['d', 'b']) + +would return: false + EOS + ) do |arguments| + + raise(Puppet::ParseError, "member(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments[0] + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'member(): Requires array to work with') + end + + unless arguments[1].is_a? String or arguments[1].is_a? Fixnum or arguments[1].is_a? Array + raise(Puppet::ParseError, 'member(): Item to search for must be a string, fixnum, or array') + end + + if arguments[1].is_a? String or arguments[1].is_a? Fixnum + item = [arguments[1]] + else + item = arguments[1] + end + + + raise(Puppet::ParseError, 'member(): You must provide item to search for within array given') if item.respond_to?('empty?') && item.empty? + + result = (item - array).empty? + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/merge.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/merge.rb new file mode 100644 index 0000000..1b39f20 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/merge.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + Merges two or more hashes together and returns the resulting hash. + + For example: + + $hash1 = {'one' => 1, 'two', => 2} + $hash2 = {'two' => 'dos', 'three', => 'tres'} + $merged_hash = merge($hash1, $hash2) + # The resulting hash is equivalent to: + # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'} + + When there is a duplicate key, the key in the rightmost hash will "win." + + ENDHEREDOC + + if args.length < 2 + raise Puppet::ParseError, ("merge(): wrong number of arguments (#{args.length}; must be at least 2)") + end + + # The hash we accumulate into + accumulator = Hash.new + # Merge into the accumulator hash + args.each do |arg| + next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef + unless arg.is_a?(Hash) + raise Puppet::ParseError, "merge: unexpected argument type #{arg.class}, only expects hash arguments" + end + accumulator.merge!(arg) + end + # Return the fully merged hash + accumulator + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/min.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/min.rb new file mode 100644 index 0000000..f10a2b2 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/min.rb @@ -0,0 +1,20 @@ +module Puppet::Parser::Functions + newfunction(:min, :type => :rvalue, :doc => <<-EOS + Returns the lowest value of all arguments. + Requires at least one argument. + EOS + ) do |args| + + raise(Puppet::ParseError, "min(): Wrong number of arguments need at least one") if args.size == 0 + + # Sometimes we get numbers as numerics and sometimes as strings. + # We try to compare them as numbers when possible + return args.min do |a,b| + if a.to_s =~ /\A^-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then + a.to_f <=> b.to_f + else + a.to_s <=> b.to_s + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/num2bool.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/num2bool.rb new file mode 100644 index 0000000..9ad59b2 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/num2bool.rb @@ -0,0 +1,42 @@ +# +# num2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS +This function converts a number or a string representation of a number into a +true boolean. Zero or anything non-numeric becomes false. Numbers higher then 0 +become true. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "num2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + number = arguments[0] + + case number + when Numeric + # Yay, it's a number + when String + begin + number = Float(number) + rescue ArgumentError => ex + raise(Puppet::ParseError, "num2bool(): '#{number}' does not look like a number: #{ex.message}") + end + else + begin + number = number.to_s + rescue NoMethodError => ex + raise(Puppet::ParseError, "num2bool(): Unable to parse argument: #{ex.message}") + end + end + + # Truncate Floats + number = number.to_i + + # Return true for any positive number and false otherwise + return number > 0 + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/parsejson.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/parsejson.rb new file mode 100644 index 0000000..f7c2896 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/parsejson.rb @@ -0,0 +1,29 @@ +# +# parsejson.rb +# + +module Puppet::Parser::Functions + newfunction(:parsejson, :type => :rvalue, :doc => <<-EOS +This function accepts JSON as a string and converts it into the correct +Puppet structure. + +The optional second argument can be used to pass a default value that will +be returned if the parsing of YAML string have failed. + EOS + ) do |arguments| + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless arguments.length >= 1 + + begin + PSON::load(arguments[0]) || arguments[1] + rescue StandardError => e + if arguments[1] + arguments[1] + else + raise e + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb new file mode 100644 index 0000000..ba9d98a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb @@ -0,0 +1,33 @@ +# +# parseyaml.rb +# + +module Puppet::Parser::Functions + newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS +This function accepts YAML as a string and converts it into the correct +Puppet structure. + +The optional second argument can be used to pass a default value that will +be returned if the parsing of YAML string have failed. + EOS + ) do |arguments| + raise ArgumentError, 'Wrong number of arguments. 1 or 2 arguments should be provided.' unless arguments.length >= 1 + require 'yaml' + + begin + YAML::load(arguments[0]) || arguments[1] + # in ruby 1.9.3 Psych::SyntaxError is a RuntimeException + # this still needs to catch that and work also on rubies that + # do not have Psych available. + rescue StandardError, Psych::SyntaxError => e + if arguments[1] + arguments[1] + else + raise e + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/pick.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/pick.rb new file mode 100644 index 0000000..fdd0aef --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/pick.rb @@ -0,0 +1,29 @@ +module Puppet::Parser::Functions + newfunction(:pick, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). Typically, +this function is used to check for a value in the Puppet Dashboard/Enterprise +Console, and failover to a default value like the following: + + $real_jenkins_version = pick($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +EOS +) do |args| + args = args.compact + args.delete(:undef) + args.delete(:undefined) + args.delete("") + if args[0].to_s.empty? then + fail Puppet::ParseError, "pick(): must receive at least one non empty value" + else + return args[0] + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/pick_default.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/pick_default.rb new file mode 100644 index 0000000..36e33ab --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/pick_default.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + newfunction(:pick_default, :type => :rvalue, :doc => <<-EOS + +This function is similar to a coalesce function in SQL in that it will return +the first value in a list of values that is not undefined or an empty string +(two things in Puppet that will return a boolean false value). If no value is +found, it will return the last argument. + +Typically, this function is used to check for a value in the Puppet +Dashboard/Enterprise Console, and failover to a default value like the +following: + + $real_jenkins_version = pick_default($::jenkins_version, '1.449') + +The value of $real_jenkins_version will first look for a top-scope variable +called 'jenkins_version' (note that parameters set in the Puppet Dashboard/ +Enterprise Console are brought into Puppet as top-scope variables), and, +failing that, will use a default value of 1.449. + +Note that, contrary to the pick() function, the pick_default does not fail if +all arguments are empty. This allows pick_default to use an empty value as +default. + +EOS +) do |args| + fail "Must receive at least one argument." if args.empty? + default = args.last + args = args[0..-2].compact + args.delete(:undef) + args.delete(:undefined) + args.delete("") + args << default + return args[0] + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/prefix.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/prefix.rb new file mode 100644 index 0000000..b1c1e35 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/prefix.rb @@ -0,0 +1,51 @@ +# +# prefix.rb +# + +module Puppet::Parser::Functions + newfunction(:prefix, :type => :rvalue, :doc => <<-EOS +This function applies a prefix to all elements in an array or a hash. + +*Examples:* + + prefix(['a','b','c'], 'p') + +Will return: ['pa','pb','pc'] + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "prefix(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + enumerable = arguments[0] + + unless enumerable.is_a?(Array) or enumerable.is_a?(Hash) + raise Puppet::ParseError, "prefix(): expected first argument to be an Array or a Hash, got #{enumerable.inspect}" + end + + prefix = arguments[1] if arguments[1] + + if prefix + unless prefix.is_a?(String) + raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}" + end + end + + if enumerable.is_a?(Array) + # Turn everything into string same as join would do ... + result = enumerable.collect do |i| + i = i.to_s + prefix ? prefix + i : i + end + else + result = Hash[enumerable.map do |k,v| + k = k.to_s + [ prefix ? prefix + k : k, v ] + end] + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/private.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/private.rb new file mode 100644 index 0000000..3b00ba1 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/private.rb @@ -0,0 +1,17 @@ +# +# private.rb +# + +module Puppet::Parser::Functions + newfunction(:private, :doc => <<-'EOS' + DEPRECATED: Sets the current class or definition as private. + Calling the class or definition from outside the current module will fail. + EOS + ) do |args| + warning("private() DEPRECATED: This function will cease to function on Puppet 4; please use assert_private() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + if !Puppet::Parser::Functions.autoloader.loaded?(:assert_private) + Puppet::Parser::Functions.autoloader.load(:assert_private) + end + function_assert_private([(args[0] unless args.size < 1)]) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/pry.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/pry.rb new file mode 100644 index 0000000..c18ef7e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/pry.rb @@ -0,0 +1,30 @@ +# +# pry.rb +# + +module Puppet::Parser::Functions + newfunction(:pry, :type => :statement, :doc => <<-EOS +This function invokes a pry debugging session in the current scope object. This is useful for debugging manifest code at specific points during a compilation. + +*Examples:* + + pry() + EOS + ) do |arguments| + begin + require 'pry' + rescue LoadError + raise(Puppet::Error, "pry(): Requires the 'pry' rubygem to use, but it was not found") + end + # + ## Run `catalog` to see the contents currently compiling catalog + ## Run `cd catalog` and `ls` to see catalog methods and instance variables + ## Run `@resource_table` to see the current catalog resource table + # + if $stdout.isatty + binding.pry # rubocop:disable Lint/Debugger + else + Puppet.warning 'pry(): cowardly refusing to start the debugger on a daemonized master' + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb new file mode 100644 index 0000000..d99ee5b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/pw_hash.rb @@ -0,0 +1,59 @@ + +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. + +Puppet::Parser::Functions::newfunction( + :pw_hash, + :type => :rvalue, + :arity => 3, + :doc => "Hashes a password using the crypt function. Provides a hash + usable on most POSIX systems. + + The first argument to this function is the password to hash. If it is + undef or an empty string, this function returns undef. + + The second argument to this function is which type of hash to use. It + will be converted into the appropriate crypt(3) hash specifier. Valid + hash types are: + + |Hash type |Specifier| + |---------------------|---------| + |MD5 |1 | + |SHA-256 |5 | + |SHA-512 (recommended)|6 | + + The third argument to this function is the salt to use. + + Note: this uses the Puppet Master's implementation of crypt(3). If your + environment contains several different operating systems, ensure that they + are compatible before using this function.") do |args| + raise ArgumentError, "pw_hash(): wrong number of arguments (#{args.size} for 3)" if args.size != 3 + raise ArgumentError, "pw_hash(): first argument must be a string" unless args[0].is_a? String or args[0].nil? + raise ArgumentError, "pw_hash(): second argument must be a string" unless args[1].is_a? String + hashes = { 'md5' => '1', + 'sha-256' => '5', + 'sha-512' => '6' } + hash_type = hashes[args[1].downcase] + raise ArgumentError, "pw_hash(): #{args[1]} is not a valid hash type" if hash_type.nil? + raise ArgumentError, "pw_hash(): third argument must be a string" unless args[2].is_a? String + raise ArgumentError, "pw_hash(): third argument must not be empty" if args[2].empty? + raise ArgumentError, "pw_hash(): characters in salt must be in the set [a-zA-Z0-9./]" unless args[2].match(/\A[a-zA-Z0-9.\/]+\z/) + + password = args[0] + return nil if password.nil? or password.empty? + + salt = "$#{hash_type}$#{args[2]}" + + # handle weak implementations of String#crypt + if 'test'.crypt('$1$1') != '$1$1$Bp8CU9Oujr9SSEw53WV6G.' + # JRuby < 1.7.17 + if RUBY_PLATFORM == 'java' + # puppetserver bundles Apache Commons Codec + org.apache.commons.codec.digest.Crypt.crypt(password.to_java_bytes, salt) + else + # MS Windows and other systems that don't support enhanced salts + raise Puppet::ParseError, 'system does not support enhanced salts' + end + else + password.crypt(salt) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/range.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/range.rb new file mode 100644 index 0000000..72c373a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/range.rb @@ -0,0 +1,85 @@ +# +# range.rb +# + +# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ... + +module Puppet::Parser::Functions + newfunction(:range, :type => :rvalue, :doc => <<-EOS +When given range in the form of (start, stop) it will extrapolate a range as +an array. + +*Examples:* + + range("0", "9") + +Will return: [0,1,2,3,4,5,6,7,8,9] + + range("00", "09") + +Will return: [0,1,2,3,4,5,6,7,8,9] (Zero padded strings are converted to +integers automatically) + + range("a", "c") + +Will return: ["a","b","c"] + + range("host01", "host10") +Will return: ["host01", "host02", ..., "host09", "host10"] +NB Be explicit in including trailing zeros. Otherwise the underlying ruby function will fail. + +Passing a third argument will cause the generated range to step by that +interval, e.g. + + range("0", "9", "2") + +Will return: [0,2,4,6,8] + EOS + ) do |arguments| + + raise(Puppet::ParseError, 'range(): Wrong number of arguments given (0 for 1)') if arguments.size == 0 + + if arguments.size > 1 + start = arguments[0] + stop = arguments[1] + step = arguments[2].nil? ? 1 : arguments[2].to_i.abs + + type = '..' # Use the simplest type of Range available in Ruby + + else # arguments.size == 1 + value = arguments[0] + + if m = value.match(/^(\w+)(\.\.\.?|\-)(\w+)$/) + start = m[1] + stop = m[3] + + type = m[2] + step = 1 + elsif value.match(/^.+$/) + raise(Puppet::ParseError, "range(): Unable to compute range from the value: #{value}") + else + raise(Puppet::ParseError, "range(): Unknown range format: #{value}") + end + end + + # If we were given an integer, ensure we work with one + if start.to_s.match(/^\d+$/) + start = start.to_i + stop = stop.to_i + else + start = start.to_s + stop = stop.to_s + end + + range = case type + when /^(\.\.|\-)$/ then (start .. stop) + when '...' then (start ... stop) # Exclusive of last element + end + + result = range.step(step).to_a + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb new file mode 100644 index 0000000..2cfa3bb --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/regexpescape.rb @@ -0,0 +1,29 @@ +# +# regexpescape.rb +# +module Puppet::Parser::Functions + newfunction(:regexpescape, :type => :rvalue, :doc => <<-EOS + Regexp escape a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| # rubocop:disable Style/ClosingParenthesisIndentation + raise(Puppet::ParseError, "regexpescape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.empty? + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'regexpescape(): Requires either array or string to work with') + end + + result = if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + value.collect { |i| i.is_a?(String) ? Regexp.escape(i) : i } + else + Regexp.escape(value) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/reject.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/reject.rb new file mode 100644 index 0000000..1953ffc --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/reject.rb @@ -0,0 +1,31 @@ +# +# reject.rb +# + +module Puppet::Parser::Functions + newfunction(:reject, :type => :rvalue, :doc => <<-EOS) do |args| +This function searches through an array and rejects all elements that match +the provided regular expression. + +*Examples:* + + reject(['aaa','bbb','ccc','aaaddd'], 'aaa') + +Would return: + + ['bbb','ccc'] +EOS + + if (args.size != 2) + raise Puppet::ParseError, + "reject(): Wrong number of arguments given #{args.size} for 2" + end + + ary = args[0] + pattern = Regexp.new(args[1]) + + ary.reject { |e| e =~ pattern } + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/reverse.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/reverse.rb new file mode 100644 index 0000000..aca98ce --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/reverse.rb @@ -0,0 +1,25 @@ +# +# reverse.rb +# + +module Puppet::Parser::Functions + newfunction(:reverse, :type => :rvalue, :doc => <<-EOS +Reverses the order of a string or array. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "reverse(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'reverse(): Requires either array or string to work with') + end + + result = value.reverse + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/rstrip.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/rstrip.rb new file mode 100644 index 0000000..e24abd5 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/rstrip.rb @@ -0,0 +1,29 @@ +# +# rstrip.rb +# + +module Puppet::Parser::Functions + newfunction(:rstrip, :type => :rvalue, :doc => <<-EOS +Strips leading spaces to the right of the string. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "rstrip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'rstrip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + result = value.collect { |i| i.is_a?(String) ? i.rstrip : i } + else + result = value.rstrip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb new file mode 100644 index 0000000..44e27b8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb @@ -0,0 +1,22 @@ +Puppet::Parser::Functions::newfunction( + :seeded_rand, + :arity => 2, + :type => :rvalue, + :doc => <<-EOS +Usage: `seeded_rand(MAX, SEED)`. MAX must be a positive integer; SEED is any string. + +Generates a random whole number greater than or equal to 0 and less +than MAX, using the value of SEED for repeatable randomness. If SEED +starts with "$fqdn:", this is behaves the same as `fqdn_rand`. + +EOS +) do |args| + require 'digest/md5' + + raise(ArgumentError, "seeded_rand(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0 + raise(ArgumentError, "seeded_rand(): second argument must be a string") unless args[1].is_a? String + + max = args[0].to_i + seed = Digest::MD5.hexdigest(args[1]).hex + Puppet::Util.deterministic_rand(seed,max) +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb new file mode 100644 index 0000000..7306b7c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_escape.rb @@ -0,0 +1,29 @@ +# +# shell_escape.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_escape, :type => :rvalue, :doc => <<-EOS +Escapes a string so that it can be safely used in a Bourne shell command line. + +Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single +quotes. + +This function behaves the same as ruby's Shellwords.shellescape() function. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_escape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + # explicit conversion to string is required for ruby 1.9 + string = arguments[0].to_s + + result = Shellwords.shellescape(string) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_join.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_join.rb new file mode 100644 index 0000000..682ed8d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_join.rb @@ -0,0 +1,30 @@ +# +# shell_join.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_join, :type => :rvalue, :doc => <<-EOS +Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are +then joined together, with a single space in between. + +This function behaves the same as ruby's Shellwords.shelljoin() function + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_join(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + array = arguments[0] + + raise Puppet::ParseError, ("First argument is not an Array: #{array.inspect}") unless array.is_a?(Array) + + # explicit conversion to string is required for ruby 1.9 + array = array.map { |item| item.to_s } + result = Shellwords.shelljoin(array) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_split.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_split.rb new file mode 100644 index 0000000..09e6e78 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/shell_split.rb @@ -0,0 +1,25 @@ +# +# shell_split.rb +# + +require 'shellwords' + +module Puppet::Parser::Functions + newfunction(:shell_split, :type => :rvalue, :doc => <<-EOS +Splits a string into an array of tokens in the same way the Bourne shell does. + +This function behaves the same as ruby's Shellwords.shellsplit() function + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shell_split(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + string = arguments[0].to_s + + result = Shellwords.shellsplit(string) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/shuffle.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/shuffle.rb new file mode 100644 index 0000000..942cbce --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/shuffle.rb @@ -0,0 +1,43 @@ +# +# shuffle.rb +# + +module Puppet::Parser::Functions + newfunction(:shuffle, :type => :rvalue, :doc => <<-EOS +Randomizes the order of a string or array elements. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "shuffle(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'shuffle(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # Check whether it makes sense to shuffle ... + return result if result.size <= 1 + + # We turn any string value into an array to be able to shuffle ... + result = string ? result.split('') : result + + elements = result.size + + # Simple implementation of Fisher–Yates in-place shuffle ... + elements.times do |i| + j = rand(elements - i) + i + result[j], result[i] = result[i], result[j] + end + + result = string ? result.join : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/size.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/size.rb new file mode 100644 index 0000000..b503aa0 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/size.rb @@ -0,0 +1,46 @@ +# +# size.rb +# + +module Puppet::Parser::Functions + newfunction(:size, :type => :rvalue, :doc => <<-EOS +Returns the number of elements in a string, an array or a hash + EOS + ) do |arguments| + + raise(Puppet::ParseError, "size(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + item = arguments[0] + + function_deprecation([:size, 'This method is going to be deprecated, please use the stdlib length function.']) + + if item.is_a?(String) + + begin + # + # Check whether your item is a numeric value or not ... + # This will take care about positive and/or negative numbers + # for both integer and floating-point values ... + # + # Please note that Puppet has no notion of hexadecimal + # nor octal numbers for its DSL at this point in time ... + # + Float(item) + + raise(Puppet::ParseError, 'size(): Requires either string, array or hash to work with') + + rescue ArgumentError + result = item.size + end + + elsif item.is_a?(Array) || item.is_a?(Hash) + result = item.size + else + raise(Puppet::ParseError, 'size(): Unknown type given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/sort.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/sort.rb new file mode 100644 index 0000000..d7792f5 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/sort.rb @@ -0,0 +1,27 @@ +# +# sort.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:sort, :type => :rvalue, :doc => <<-EOS +Sorts strings and arrays lexically. + EOS + ) do |arguments| + + if (arguments.size != 1) then + raise(Puppet::ParseError, "sort(): Wrong number of arguments given #{arguments.size} for 1") + end + + value = arguments[0] + + if value.is_a?(Array) then + value.sort + elsif value.is_a?(String) then + value.split("").sort.join("") + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/squeeze.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/squeeze.rb new file mode 100644 index 0000000..f5757d1 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/squeeze.rb @@ -0,0 +1,35 @@ +# +# squeeze.rb +# + +module Puppet::Parser::Functions + newfunction(:squeeze, :type => :rvalue, :doc => <<-EOS +Returns a new string where runs of the same character that occur in this set are replaced by a single character. + EOS + ) do |arguments| + + if ((arguments.size != 2) and (arguments.size != 1)) then + raise(Puppet::ParseError, "squeeze(): Wrong number of arguments given #{arguments.size} for 2 or 1") + end + + item = arguments[0] + squeezeval = arguments[1] + + if item.is_a?(Array) then + if squeezeval then + item.collect { |i| i.squeeze(squeezeval) } + else + item.collect { |i| i.squeeze } + end + else + if squeezeval then + item.squeeze(squeezeval) + else + item.squeeze + end + end + + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/str2bool.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/str2bool.rb new file mode 100644 index 0000000..38ad1ce --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/str2bool.rb @@ -0,0 +1,44 @@ +# +# str2bool.rb +# + +module Puppet::Parser::Functions + newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS +This converts a string to a boolean. This attempt to convert strings that +contain things like: Y,y, 1, T,t, TRUE,true to 'true' and strings that contain things +like: 0, F,f, N,n, false, FALSE, no to 'false'. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "str2bool(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + string = arguments[0] + + # If string is already Boolean, return it + if !!string == string + return string + end + + unless string.is_a?(String) + raise(Puppet::ParseError, 'str2bool(): Requires string to work with') + end + + # We consider all the yes, no, y, n and so on too ... + result = case string + # + # This is how undef looks like in Puppet ... + # We yield false in this case. + # + when /^$/, '' then false # Empty string will be false ... + when /^(1|t|y|true|yes)$/i then true + when /^(0|f|n|false|no)$/i then false + when /^(undef|undefined)$/ then false # This is not likely to happen ... + else + raise(Puppet::ParseError, 'str2bool(): Unknown type of boolean given') + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb new file mode 100644 index 0000000..0410c62 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb @@ -0,0 +1,31 @@ +# +# str2saltedsha512.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:str2saltedsha512, :type => :rvalue, :doc => <<-EOS +This converts a string to a salted-SHA512 password hash (which is used for +OS X versions >= 10.7). Given any simple string, you will get a hex version +of a salted-SHA512 password hash that can be inserted into your Puppet +manifests as a valid password attribute. + EOS + ) do |arguments| + require 'digest/sha2' + + raise(Puppet::ParseError, "str2saltedsha512(): Wrong number of arguments passed (#{arguments.size} but we require 1)") if arguments.size != 1 + + password = arguments[0] + + unless password.is_a?(String) + raise(Puppet::ParseError, "str2saltedsha512(): Requires a String argument, you passed: #{password.class}") + end + + seedint = rand(2**31 - 1) + seedstring = Array(seedint).pack("L") + saltedpass = Digest::SHA512.digest(seedstring + password) + (seedstring + saltedpass).unpack('H*')[0] + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/strftime.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/strftime.rb new file mode 100644 index 0000000..deae329 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/strftime.rb @@ -0,0 +1,106 @@ +# +# strftime.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:strftime, :type => :rvalue, :doc => <<-EOS +This function returns formatted time. + +*Examples:* + +To return the time since epoch: + + strftime("%s") + +To return the date: + + strftime("%Y-%m-%d") + +*Format meaning:* + + %a - The abbreviated weekday name (``Sun'') + %A - The full weekday name (``Sunday'') + %b - The abbreviated month name (``Jan'') + %B - The full month name (``January'') + %c - The preferred local date and time representation + %C - Century (20 in 2009) + %d - Day of the month (01..31) + %D - Date (%m/%d/%y) + %e - Day of the month, blank-padded ( 1..31) + %F - Equivalent to %Y-%m-%d (the ISO 8601 date format) + %h - Equivalent to %b + %H - Hour of the day, 24-hour clock (00..23) + %I - Hour of the day, 12-hour clock (01..12) + %j - Day of the year (001..366) + %k - hour, 24-hour clock, blank-padded ( 0..23) + %l - hour, 12-hour clock, blank-padded ( 0..12) + %L - Millisecond of the second (000..999) + %m - Month of the year (01..12) + %M - Minute of the hour (00..59) + %n - Newline (\n) + %N - Fractional seconds digits, default is 9 digits (nanosecond) + %3N millisecond (3 digits) + %6N microsecond (6 digits) + %9N nanosecond (9 digits) + %p - Meridian indicator (``AM'' or ``PM'') + %P - Meridian indicator (``am'' or ``pm'') + %r - time, 12-hour (same as %I:%M:%S %p) + %R - time, 24-hour (%H:%M) + %s - Number of seconds since 1970-01-01 00:00:00 UTC. + %S - Second of the minute (00..60) + %t - Tab character (\t) + %T - time, 24-hour (%H:%M:%S) + %u - Day of the week as a decimal, Monday being 1. (1..7) + %U - Week number of the current year, + starting with the first Sunday as the first + day of the first week (00..53) + %v - VMS date (%e-%b-%Y) + %V - Week number of year according to ISO 8601 (01..53) + %W - Week number of the current year, + starting with the first Monday as the first + day of the first week (00..53) + %w - Day of the week (Sunday is 0, 0..6) + %x - Preferred representation for the date alone, no time + %X - Preferred representation for the time alone, no date + %y - Year without a century (00..99) + %Y - Year with century + %z - Time zone as hour offset from UTC (e.g. +0900) + %Z - Time zone name + %% - Literal ``%'' character + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "strftime(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + format = arguments[0] + + raise(Puppet::ParseError, 'strftime(): You must provide format for evaluation') if format.empty? + + # The Time Zone argument is optional ... + time_zone = arguments[1] if arguments[1] + + time = Time.new + + # There is probably a better way to handle Time Zone ... + if time_zone and not time_zone.empty? + original_zone = ENV['TZ'] + + local_time = time.clone + local_time = local_time.utc + + ENV['TZ'] = time_zone + + time = local_time.localtime + + ENV['TZ'] = original_zone + end + + result = time.strftime(format) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/strip.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/strip.rb new file mode 100644 index 0000000..9e8366f --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/strip.rb @@ -0,0 +1,36 @@ +# +# strip.rb +# + +module Puppet::Parser::Functions + newfunction(:strip, :type => :rvalue, :doc => <<-EOS +This function removes leading and trailing whitespace from a string or from +every string inside an array. + +*Examples:* + + strip(" aaa ") + +Would result in: "aaa" + EOS + ) do |arguments| + + raise(Puppet::ParseError, "strip(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'strip(): Requires either array or string to work with') + end + + if value.is_a?(Array) + result = value.collect { |i| i.is_a?(String) ? i.strip : i } + else + result = value.strip + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/suffix.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/suffix.rb new file mode 100644 index 0000000..7c5057d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/suffix.rb @@ -0,0 +1,52 @@ +# +# suffix.rb +# + +module Puppet::Parser::Functions + newfunction(:suffix, :type => :rvalue, :doc => <<-EOS +This function applies a suffix to all elements in an array, or to the keys +in a hash. + +*Examples:* + + suffix(['a','b','c'], 'p') + +Will return: ['ap','bp','cp'] + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "suffix(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + enumerable = arguments[0] + + unless enumerable.is_a?(Array) or enumerable.is_a?(Hash) + raise Puppet::ParseError, "suffix(): expected first argument to be an Array or a Hash, got #{enumerable.inspect}" + end + + suffix = arguments[1] if arguments[1] + + if suffix + unless suffix.is_a? String + raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}" + end + end + + if enumerable.is_a?(Array) + # Turn everything into string same as join would do ... + result = enumerable.collect do |i| + i = i.to_s + suffix ? i + suffix : i + end + else + result = Hash[enumerable.map do |k,v| + k = k.to_s + [ suffix ? k + suffix : k, v ] + end] + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/swapcase.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/swapcase.rb new file mode 100644 index 0000000..f3276fb --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/swapcase.rb @@ -0,0 +1,37 @@ +# +# swapcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:swapcase, :type => :rvalue, :doc => <<-EOS +This function will swap the existing case of a string. + +*Examples:* + + swapcase("aBcD") + +Would result in: "AbCd" + EOS + ) do |arguments| + + raise(Puppet::ParseError, "swapcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'swapcase(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? i.swapcase : i } + else + result = value.swapcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/time.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/time.rb new file mode 100644 index 0000000..d7780c8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/time.rb @@ -0,0 +1,49 @@ +# +# time.rb +# + +module Puppet::Parser::Functions + newfunction(:time, :type => :rvalue, :doc => <<-EOS +This function will return the current time since epoch as an integer. + +*Examples:* + + time() + +Will return something like: 1311972653 + EOS + ) do |arguments| + + # The Time Zone argument is optional ... + time_zone = arguments[0] if arguments[0] + + if (arguments.size != 0) and (arguments.size != 1) then + raise(Puppet::ParseError, "time(): Wrong number of arguments given #{arguments.size} for 0 or 1") + end + + time = Time.new + + # There is probably a better way to handle Time Zone ... + if time_zone and not time_zone.empty? + original_zone = ENV['TZ'] + + local_time = time.clone + local_time = local_time.utc + + ENV['TZ'] = time_zone + + result = local_time.localtime.strftime('%s') + + ENV['TZ'] = original_zone + else + result = time.localtime.strftime('%s') + end + + # Calling Time#to_i on a receiver changes it. Trust me I am the Doctor. + result = result.to_i + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb new file mode 100644 index 0000000..bf72503 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb @@ -0,0 +1,30 @@ +module Puppet::Parser::Functions + newfunction(:to_bytes, :type => :rvalue, :doc => <<-EOS + Converts the argument into bytes, for example 4 kB becomes 4096. + Takes a single string value as an argument. + These conversions reflect a layperson's understanding of + 1 MB = 1024 KB, when in fact 1 MB = 1000 KB, and 1 MiB = 1024 KiB. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "to_bytes(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + arg = arguments[0] + + return arg if arg.is_a? Numeric + + value,prefix = */([0-9.e+-]*)\s*([^bB]?)/.match(arg)[1,2] + + value = value.to_f + case prefix + when '' then return value.to_i + when 'k' then return (value*(1<<10)).to_i + when 'M' then return (value*(1<<20)).to_i + when 'G' then return (value*(1<<30)).to_i + when 'T' then return (value*(1<<40)).to_i + when 'P' then return (value*(1<<50)).to_i + when 'E' then return (value*(1<<60)).to_i + else raise Puppet::ParseError, "to_bytes(): Unknown prefix #{prefix}" + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb new file mode 100644 index 0000000..fc19a23 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/try_get_value.rb @@ -0,0 +1,53 @@ +module Puppet::Parser::Functions + newfunction( + :try_get_value, + :type => :rvalue, + :arity => -2, + :doc => <<-eos +DEPRECATED: this function is deprecated, please use dig() instead. + +Looks up into a complex structure of arrays and hashes and returns a value +or the default value if nothing was found. + +Key can contain slashes to describe path components. The function will go down +the structure and try to extract the required value. + +$data = { + 'a' => { + 'b' => [ + 'b1', + 'b2', + 'b3', + ] + } +} + +$value = try_get_value($data, 'a/b/2', 'not_found', '/') +=> $value = 'b3' + +a -> first hash key +b -> second hash key +2 -> array index starting with 0 + +not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil. +/ -> (optional) path delimiter. Defaults to '/'. + +In addition to the required "key" argument, "try_get_value" accepts default +argument. It will be returned if no value was found or a path component is +missing. And the fourth argument can set a variable path separator. + eos + ) do |args| + warning("try_get_value() DEPRECATED: this function is deprecated, please use dig() instead.") + data = args[0] + path = args[1] || '' + default = args[2] + + if !(data.is_a?(Hash) || data.is_a?(Array)) || path == '' + return default || data + end + + separator = args[3] || '/' + path = path.split(separator).map{ |key| key =~ /^\d+$/ ? key.to_i : key } + function_dig([data, path, default]) + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/type.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/type.rb new file mode 100644 index 0000000..016529b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/type.rb @@ -0,0 +1,19 @@ +# +# type.rb +# + +module Puppet::Parser::Functions + newfunction(:type, :type => :rvalue, :doc => <<-EOS + DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system. + EOS + ) do |args| + + warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x) + Puppet::Parser::Functions.autoloader.load(:type3x) + end + function_type3x(args + [false]) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/type3x.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/type3x.rb new file mode 100644 index 0000000..b17380e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/type3x.rb @@ -0,0 +1,50 @@ +# +# type3x.rb +# + +module Puppet::Parser::Functions + newfunction(:type3x, :type => :rvalue, :doc => <<-EOS +DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system. + +Returns the type when passed a value. Type can be one of: + +* string +* array +* hash +* float +* integer +* boolean + EOS + ) do |args| + raise(Puppet::ParseError, "type3x(): Wrong number of arguments given (#{args.size} for 1)") if args.size < 1 + + value = args[0] + + klass = value.class + + if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass) + raise(Puppet::ParseError, 'type3x(): Unknown type') + end + + klass = klass.to_s # Ugly ... + + # We note that Integer is the parent to Bignum and Fixnum ... + result = case klass + when /^(?:Big|Fix)num$/ then 'integer' + when /^(?:True|False)Class$/ then 'boolean' + else klass + end + + if result == "String" then + if value == value.to_i.to_s then + result = "Integer" + elsif value == value.to_f.to_s then + result = "Float" + end + end + + return result.downcase + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/union.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/union.rb new file mode 100644 index 0000000..abe2dc8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/union.rb @@ -0,0 +1,28 @@ +# +# union.rb +# + +module Puppet::Parser::Functions + newfunction(:union, :type => :rvalue, :doc => <<-EOS +This function returns a union of two or more arrays. + +*Examples:* + + union(["a","b","c"],["b","c","d"]) + +Would return: ["a","b","c","d"] + EOS + ) do |arguments| + + # Check that 2 or more arguments have been given ... + raise(Puppet::ParseError, "union(): Wrong number of arguments given (#{arguments.size} for < 2)") if arguments.size < 2 + + arguments.each do |argument| + raise(Puppet::ParseError, 'union(): Every parameter must be an array') unless argument.is_a?(Array) + end + + arguments.reduce(:|) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/unique.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/unique.rb new file mode 100644 index 0000000..b57431d --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/unique.rb @@ -0,0 +1,48 @@ +# +# unique.rb +# + +module Puppet::Parser::Functions + newfunction(:unique, :type => :rvalue, :doc => <<-EOS +This function will remove duplicates from strings and arrays. + +*Examples:* + + unique("aabbcc") + +Will return: + + abc + +You can also use this with arrays: + + unique(["a","a","b","b","c","c"]) + +This returns: + + ["a","b","c"] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "unique(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'unique(): Requires either array or string to work with') + end + + result = value.clone + + string = value.is_a?(String) ? true : false + + # We turn any string value into an array to be able to shuffle ... + result = string ? result.split('') : result + result = result.uniq # Remove duplicates ... + result = string ? result.join : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb new file mode 100644 index 0000000..0bd9cd1 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/unix2dos.rb @@ -0,0 +1,15 @@ +# Custom Puppet function to convert unix to dos format +module Puppet::Parser::Functions + newfunction(:unix2dos, :type => :rvalue, :arity => 1, :doc => <<-EOS + Returns the DOS version of the given string. + Takes a single string argument. + EOS + ) do |arguments| + + unless arguments[0].is_a?(String) + raise(Puppet::ParseError, 'unix2dos(): Requires string as argument') + end + + arguments[0].gsub(/\r*\n/, "\r\n") + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/upcase.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/upcase.rb new file mode 100644 index 0000000..e847e51 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/upcase.rb @@ -0,0 +1,44 @@ +# +# upcase.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# + +module Puppet::Parser::Functions + newfunction(:upcase, :type => :rvalue, :doc => <<-EOS +Converts a string or an array of strings to uppercase. + +*Examples:* + + upcase("abcd") + +Will return: + + ABCD + EOS + ) do |arguments| + + raise(Puppet::ParseError, "upcase(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size != 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(Hash) || value.respond_to?(:upcase) + raise(Puppet::ParseError, 'upcase(): Requires an array, hash or object that responds to upcase in order to work') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| function_upcase([i]) } + elsif value.is_a?(Hash) + result = {} + value.each_pair do |k, v| + result[function_upcase([k])] = function_upcase([v]) + end + else + result = value.upcase + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/uriescape.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/uriescape.rb new file mode 100644 index 0000000..4c5c400 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/uriescape.rb @@ -0,0 +1,33 @@ +# +# uriescape.rb +# Please note: This function is an implementation of a Ruby class and as such may not be entirely UTF8 compatible. To ensure compatibility please use this function with Ruby 2.4.0 or greater - https://bugs.ruby-lang.org/issues/10085. +# +require 'uri' + +module Puppet::Parser::Functions + newfunction(:uriescape, :type => :rvalue, :doc => <<-EOS + Urlencodes a string or array of strings. + Requires either a single string or an array as an input. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "uriescape(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(String) + raise(Puppet::ParseError, 'uriescape(): Requires either array or string to work with') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| i.is_a?(String) ? URI.escape(i) : i } + else + result = URI.escape(value) + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb new file mode 100644 index 0000000..c73f3df --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb @@ -0,0 +1,54 @@ +module Puppet::Parser::Functions + newfunction(:validate_absolute_path, :doc => <<-'ENDHEREDOC') do |args| + Validate the string represents an absolute path in the filesystem. This function works + for windows and unix style paths. + + The following values will pass: + + $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet' + validate_absolute_path($my_path) + $my_path2 = '/var/lib/puppet' + validate_absolute_path($my_path2) + $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet'] + validate_absolute_path($my_path3) + $my_path4 = ['/var/lib/puppet','/usr/share/puppet'] + validate_absolute_path($my_path4) + + The following values will fail, causing compilation to abort: + + validate_absolute_path(true) + validate_absolute_path('../var/lib/puppet') + validate_absolute_path('var/lib/puppet') + validate_absolute_path([ 'var/lib/puppet', '/var/foo' ]) + validate_absolute_path([ '/var/lib/puppet', 'var/foo' ]) + $undefined = undef + validate_absolute_path($undefined) + + ENDHEREDOC + + # The deprecation function was being called twice, as validate_absolute_path calls is_absolute_path. I have removed it from here so it only calls deprecation once within is_absolute_path. + # function_deprecation([:validate_absolute_path, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Absolute_path. There is further documentation for validate_legacy function in the README.']) + + require 'puppet/util' + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_absolute_path(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + # put arg to candidate var to be able to replace it + candidates = arg + # if arg is just a string with a path to test, convert it to an array + # to avoid test code duplication + unless arg.is_a?(Array) then + candidates = Array.new(1,arg) + end + # iterate over all paths within the candidates array + candidates.each do |path| + unless function_is_absolute_path([path]) + raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.") + end + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_array.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_array.rb new file mode 100644 index 0000000..3bf3983 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_array.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:validate_array, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are array data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_array = [ 'one', 'two' ] + validate_array($my_array) + + The following values will fail, causing compilation to abort: + + validate_array(true) + validate_array('some_string') + $undefined = undef + validate_array($undefined) + + ENDHEREDOC + + function_deprecation([:validate_array, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Array. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_array(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(Array) + raise Puppet::ParseError, ("#{arg.inspect} is not an Array. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb new file mode 100644 index 0000000..2196c3e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb @@ -0,0 +1,83 @@ +require 'tempfile' + +module Puppet::Parser::Functions + newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args| + Perform validation of a string using an Augeas lens + The first argument of this function should be a string to + test, and the second argument should be the name of the Augeas lens to use. + If Augeas fails to parse the string with the lens, the compilation will + abort with a parse error. + + A third argument can be specified, listing paths which should + not be found in the file. The `$file` variable points to the location + of the temporary file being tested in the Augeas tree. + + For example, if you want to make sure your passwd content never contains + a user `foo`, you could write: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) + + Or if you wanted to ensure that no users used the '/bin/barsh' shell, + you could use: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] + + If a fourth argument is specified, this will be the error message raised and + seen by the user. + + A helpful error message can be returned like this: + + validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') + + ENDHEREDOC + unless Puppet.features.augeas? + raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://docs.puppetlabs.com/guides/augeas.html#pre-requisites for how to activate it.") + end + + if (args.length < 2) or (args.length > 4) then + raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)") + end + + msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}" + + require 'augeas' + aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) + begin + content = args[0] + + # Test content in a temporary file + tmpfile = Tempfile.new("validate_augeas") + begin + tmpfile.write(content) + ensure + tmpfile.close + end + + # Check for syntax + lens = args[1] + aug.transform( + :lens => lens, + :name => 'Validate_augeas', + :incl => tmpfile.path + ) + aug.load! + + unless aug.match("/augeas/files#{tmpfile.path}//error").empty? + error = aug.get("/augeas/files#{tmpfile.path}//error/message") + msg += " with error: #{error}" + raise Puppet::ParseError, (msg) + end + + # Launch unit tests + tests = args[2] || [] + aug.defvar('file', "/files#{tmpfile.path}") + tests.each do |t| + msg += " testing path #{t}" + raise Puppet::ParseError, (msg) unless aug.match(t).empty? + end + ensure + aug.close + tmpfile.unlink + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb new file mode 100644 index 0000000..49075b8 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb @@ -0,0 +1,37 @@ +module Puppet::Parser::Functions + + newfunction(:validate_bool, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are either true or false. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $iamtrue = true + validate_bool(true) + validate_bool(true, true, false, $iamtrue) + + The following values will fail, causing compilation to abort: + + $some_array = [ true ] + validate_bool("false") + validate_bool("true") + validate_bool($some_array) + + ENDHEREDOC + + # The deprecation function was being called twice, as validate_bool calls is_bool. I have removed it from here so it only calls deprecation once within is_bool. + # function_deprecation([:validate_bool, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Bool. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_bool(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless function_is_bool([arg]) + raise Puppet::ParseError, ("#{arg.inspect} is not a boolean. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb new file mode 100644 index 0000000..685162b --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb @@ -0,0 +1,63 @@ +require 'puppet/util/execution' +require 'tempfile' + +module Puppet::Parser::Functions + newfunction(:validate_cmd, :doc => <<-'ENDHEREDOC') do |args| + Perform validation of a string with an external command. + The first argument of this function should be a string to + test, and the second argument should be a path to a test command + taking a % as a placeholder for the file path (will default to the end). + If the command, launched against a tempfile containing the passed string, + returns a non-null value, compilation will abort with a parse error. + + If a third argument is specified, this will be the error message raised and + seen by the user. + + A helpful error message can be returned like this: + + Example: + + # Defaults to end of path + validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') + + # % as file location + validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content') + + ENDHEREDOC + if (args.length < 2) or (args.length > 3) then + raise Puppet::ParseError, ("validate_cmd(): wrong number of arguments (#{args.length}; must be 2 or 3)") + end + + msg = args[2] || "validate_cmd(): failed to validate content with command #{args[1].inspect}" + + content = args[0] + checkscript = args[1] + + # Test content in a temporary file + tmpfile = Tempfile.new("validate_cmd") + begin + tmpfile.write(content) + tmpfile.close + + if checkscript =~ /\s%(\s|$)/ + check_with_correct_location = checkscript.gsub(/%/,tmpfile.path) + else + check_with_correct_location = "#{checkscript} #{tmpfile.path}" + end + + if Puppet::Util::Execution.respond_to?('execute') + Puppet::Util::Execution.execute(check_with_correct_location) + else + Puppet::Util.execute(check_with_correct_location) + end + rescue Puppet::ExecutionFailure => detail + msg += "\n#{detail}" + raise Puppet::ParseError, msg + rescue StandardError => detail + msg += "\n#{detail.class.name} #{detail}" + raise Puppet::ParseError, msg + ensure + tmpfile.unlink + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb new file mode 100644 index 0000000..ddd0d25 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_email_address.rb @@ -0,0 +1,31 @@ +module Puppet::Parser::Functions + newfunction(:validate_email_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid email addresses. + Fail compilation if any value fails this check. + The following values will pass: + $my_email = "waldo@gmail.com" + validate_email_address($my_email) + validate_email_address("bob@gmail.com", "alice@gmail.com", $my_email) + + The following values will fail, causing compilation to abort: + $some_array = [ 'bad_email@/d/efdf.com' ] + validate_email_address($some_array) + ENDHEREDOC + ) do |args| + rescuable_exceptions = [ArgumentError] + + if args.empty? + raise Puppet::ParseError, "validate_email_address(): wrong number of arguments (#{args.length}; must be > 0)" + end + + args.each do |arg| + raise Puppet::ParseError, "#{arg.inspect} is not a string." unless arg.is_a?(String) + + begin + raise Puppet::ParseError, "#{arg.inspect} is not a valid email address" unless function_is_email_address([arg]) + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid email address" + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb new file mode 100644 index 0000000..fcdc7e1 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb @@ -0,0 +1,35 @@ +module Puppet::Parser::Functions + + newfunction(:validate_hash, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are hash data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_hash = { 'one' => 'two' } + validate_hash($my_hash) + + The following values will fail, causing compilation to abort: + + validate_hash(true) + validate_hash('some_string') + $undefined = undef + validate_hash($undefined) + + ENDHEREDOC + + function_deprecation([:validate_hash, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Hash. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_hash(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(Hash) + raise Puppet::ParseError, ("#{arg.inspect} is not a Hash. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb new file mode 100644 index 0000000..2ae0293 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_integer.rb @@ -0,0 +1,134 @@ +module Puppet::Parser::Functions + + newfunction(:validate_integer, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is an integer (or an array of integers). Abort catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not an integer or array of integers, and if arg 2 and arg 3 are not convertable to an integer. + + The following values will pass: + + validate_integer(1) + validate_integer(1, 2) + validate_integer(1, 1) + validate_integer(1, 2, 0) + validate_integer(2, 2, 2) + validate_integer(2, '', 0) + validate_integer(2, undef, 0) + $foo = undef + validate_integer(2, $foo, 0) + validate_integer([1,2,3,4,5], 6) + validate_integer([1,2,3,4,5], 6, 0) + + Plus all of the above, but any combination of values passed as strings ('1' or "1"). + Plus all of the above, but with (correct) combinations of negative integer values. + + The following values will not: + + validate_integer(true) + validate_integer(false) + validate_integer(7.0) + validate_integer({ 1 => 2 }) + $foo = undef + validate_integer($foo) + validate_integer($foobaridontexist) + + validate_integer(1, 0) + validate_integer(1, true) + validate_integer(1, '') + validate_integer(1, undef) + validate_integer(1, , 0) + validate_integer(1, 2, 3) + validate_integer(1, 3, 2) + validate_integer(1, 3, true) + + Plus all of the above, but any combination of values passed as strings ('false' or "false"). + Plus all of the above, but with incorrect combinations of negative integer values. + Plus all of the above, but with non-integer items in arrays or maximum / minimum argument. + + ENDHEREDOC + + function_deprecation([:validate_integer, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Integer. There is further documentation for validate_legacy function in the README.']) + + # tell the user we need at least one, and optionally up to two other parameters + raise Puppet::ParseError, "validate_integer(): Wrong number of arguments; must be 1, 2 or 3, got #{args.length}" unless args.length > 0 and args.length < 4 + + input, max, min = *args + + # check maximum parameter + if args.length > 1 + max = max.to_s + # allow max to be empty (or undefined) if we have a minimum set + if args.length > 2 and max == '' + max = nil + else + begin + max = Integer(max) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected second argument to be unset or an Integer, got #{max}:#{max.class}" + end + end + else + max = nil + end + + # check minimum parameter + if args.length > 2 + begin + min = Integer(min.to_s) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected third argument to be unset or an Integer, got #{min}:#{min.class}" + end + else + min = nil + end + + # ensure that min < max + if min and max and min > max + raise Puppet::ParseError, "validate_integer(): Expected second argument to be larger than third argument, got #{max} < #{min}" + end + + # create lamba validator function + validator = lambda do |num| + # check input < max + if max and num > max + raise Puppet::ParseError, "validate_integer(): Expected #{input.inspect} to be smaller or equal to #{max}, got #{input.inspect}." + end + # check input > min (this will only be checked if no exception has been raised before) + if min and num < min + raise Puppet::ParseError, "validate_integer(): Expected #{input.inspect} to be greater or equal to #{min}, got #{input.inspect}." + end + end + + # if this is an array, handle it. + case input + when Array + # check every element of the array + input.each_with_index do |arg, pos| + begin + raise TypeError if arg.is_a?(Hash) + arg = Integer(arg.to_s) + validator.call(arg) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected element at array position #{pos} to be an Integer, got #{arg.class}" + end + end + # for the sake of compatibility with ruby 1.8, we need extra handling of hashes + when Hash + raise Puppet::ParseError, "validate_integer(): Expected first argument to be an Integer or Array, got #{input.class}" + # check the input. this will also fail any stuff other than pure, shiny integers + else + begin + input = Integer(input.to_s) + validator.call(input) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_integer(): Expected first argument to be an Integer or Array, got #{input.class}" + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb new file mode 100644 index 0000000..5d80cfb --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ip_address.rb @@ -0,0 +1,52 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ip_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IP addresses, + regardless they are IPv4 or IPv6 + Fail compilation if any value fails this check. + The following values will pass: + $my_ip = "1.2.3.4" + validate_ip_address($my_ip) + validate_ip_address("8.8.8.8", "172.16.0.1", $my_ip) + + $my_ip = "3ffe:505:2" + validate_ip_address(1) + validate_ip_address($my_ip) + validate_ip_address("fe80::baf6:b1ff:fe19:7507", $my_ip) + + The following values will fail, causing compilation to abort: + $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] + validate_ip_address($some_array) + ENDHEREDOC + ) do |args| + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + function_deprecation([:validate_ip_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ip_address. There is further documentation for validate_legacy function in the README.']) + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ip_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv4? or IPAddr.new(arg).ipv6? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IP address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IP address." + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb new file mode 100644 index 0000000..0660abd --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv4 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "1.2.3.4" + validate_ipv4_address($my_ip) + validate_ipv4_address("8.8.8.8", "172.16.0.1", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] + validate_ipv4_address($some_array) + + ENDHEREDOC + ) do |args| + + function_deprecation([:validate_ipv4_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv4. There is further documentation for validate_legacy function in the README.']) + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv4? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb new file mode 100644 index 0000000..f5dd9e5 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb @@ -0,0 +1,51 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv6 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "3ffe:505:2" + validate_ipv6_address(1) + validate_ipv6_address($my_ip) + validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ true, false, "garbage string", "1.2.3.4" ] + validate_ipv6_address($some_array) + + ENDHEREDOC + ) do |args| + + function_deprecation([:validate_ipv6_address, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Ipv6. There is further documentation for validate_legacy function in the README.']) + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv6? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb new file mode 100644 index 0000000..4205b30 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_numeric.rb @@ -0,0 +1,96 @@ +module Puppet::Parser::Functions + + newfunction(:validate_numeric, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is a numeric value (or an array of numeric values). Abort catalog compilation if any of the checks fail. + + The second argument is optional and passes a maximum. (All elements of) the first argument has to be less or equal to this max. + + The third argument is optional and passes a minimum. (All elements of) the first argument has to be greater or equal to this min. + If, and only if, a minimum is given, the second argument may be an empty string or undef, which will be handled to just check + if (all elements of) the first argument are greater or equal to the given minimum. + + It will fail if the first argument is not a numeric (Integer or Float) or array of numerics, and if arg 2 and arg 3 are not convertable to a numeric. + + For passing and failing usage, see `validate_integer()`. It is all the same for validate_numeric, yet now floating point values are allowed, too. + + ENDHEREDOC + + function_deprecation([:validate_numeric, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Numeric. There is further documentation for validate_legacy function in the README.']) + + # tell the user we need at least one, and optionally up to two other parameters + raise Puppet::ParseError, "validate_numeric(): Wrong number of arguments; must be 1, 2 or 3, got #{args.length}" unless args.length > 0 and args.length < 4 + + input, max, min = *args + + # check maximum parameter + if args.length > 1 + max = max.to_s + # allow max to be empty (or undefined) if we have a minimum set + if args.length > 2 and max == '' + max = nil + else + begin + max = Float(max) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected second argument to be unset or a Numeric, got #{max}:#{max.class}" + end + end + else + max = nil + end + + # check minimum parameter + if args.length > 2 + begin + min = Float(min.to_s) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected third argument to be unset or a Numeric, got #{min}:#{min.class}" + end + else + min = nil + end + + # ensure that min < max + if min and max and min > max + raise Puppet::ParseError, "validate_numeric(): Expected second argument to be larger than third argument, got #{max} < #{min}" + end + + # create lamba validator function + validator = lambda do |num| + # check input < max + if max and num > max + raise Puppet::ParseError, "validate_numeric(): Expected #{input.inspect} to be smaller or equal to #{max}, got #{input.inspect}." + end + # check input > min (this will only be checked if no exception has been raised before) + if min and num < min + raise Puppet::ParseError, "validate_numeric(): Expected #{input.inspect} to be greater or equal to #{min}, got #{input.inspect}." + end + end + + # if this is an array, handle it. + case input + when Array + # check every element of the array + input.each_with_index do |arg, pos| + begin + raise TypeError if arg.is_a?(Hash) + arg = Float(arg.to_s) + validator.call(arg) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected element at array position #{pos} to be a Numeric, got #{arg.class}" + end + end + # for the sake of compatibility with ruby 1.8, we need extra handling of hashes + when Hash + raise Puppet::ParseError, "validate_integer(): Expected first argument to be a Numeric or Array, got #{input.class}" + # check the input. this will also fail any stuff other than pure, shiny integers + else + begin + input = Float(input.to_s) + validator.call(input) + rescue TypeError, ArgumentError + raise Puppet::ParseError, "validate_numeric(): Expected first argument to be a Numeric or Array, got #{input.class}" + end + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_re.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_re.rb new file mode 100644 index 0000000..0ac83dd --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_re.rb @@ -0,0 +1,50 @@ +module Puppet::Parser::Functions + newfunction(:validate_re, :doc => <<-'ENDHEREDOC') do |args| + Perform simple validation of a string against one or more regular + expressions. The first argument of this function should be a string to + test, and the second argument should be a stringified regular expression + (without the // delimiters) or an array of regular expressions. If none + of the regular expressions match the string passed in, compilation will + abort with a parse error. + + If a third argument is specified, this will be the error message raised and + seen by the user. + + The following strings will validate against the regular expressions: + + validate_re('one', '^one$') + validate_re('one', [ '^one', '^two' ]) + + The following strings will fail to validate, causing compilation to abort: + + validate_re('one', [ '^two', '^three' ]) + + A helpful error message can be returned like this: + + validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7') + + Note: Compilation will also abort, if the first argument is not a String. Always use + quotes to force stringification: + + validate_re("${::operatingsystemmajrelease}", '^[57]$') + + ENDHEREDOC + + function_deprecation([:validate_re, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::Re. There is further documentation for validate_legacy function in the README.']) + + if (args.length < 2) or (args.length > 3) then + raise Puppet::ParseError, "validate_re(): wrong number of arguments (#{args.length}; must be 2 or 3)" + end + + raise Puppet::ParseError, "validate_re(): input needs to be a String, not a #{args[0].class}" unless args[0].is_a? String + + msg = args[2] || "validate_re(): #{args[0].inspect} does not match #{args[1].inspect}" + + # We're using a flattened array here because we can't call String#any? in + # Ruby 1.9 like we can in Ruby 1.8 + raise Puppet::ParseError, msg unless [args[1]].flatten.any? do |re_str| + args[0] =~ Regexp.compile(re_str) + end + + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb new file mode 100644 index 0000000..383855c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb @@ -0,0 +1,71 @@ +module Puppet::Parser::Functions + + newfunction(:validate_slength, :doc => <<-'ENDHEREDOC') do |args| + Validate that the first argument is a string (or an array of strings), and + less/equal to than the length of the second argument. An optional third + parameter can be given the minimum length. It fails if the first + argument is not a string or array of strings, and if arg 2 and arg 3 are + not convertable to a number. + + The following values will pass: + + validate_slength("discombobulate",17) + validate_slength(["discombobulate","moo"],17) + validate_slength(["discombobulate","moo"],17,3) + + The following valueis will not: + + validate_slength("discombobulate",1) + validate_slength(["discombobulate","thermometer"],5) + validate_slength(["discombobulate","moo"],17,10) + + ENDHEREDOC + + function_deprecation([:validate_slength, 'This method is deprecated, please use the stdlib validate_legacy function, with String[]. There is further documentation for validate_legacy function in the README.']) + + raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3 + + input, max_length, min_length = *args + + begin + max_length = Integer(max_length) + raise ArgumentError if max_length <= 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}" + end + + if min_length + begin + min_length = Integer(min_length) + raise ArgumentError if min_length < 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}" + end + else + min_length = 0 + end + + raise Puppet::ParseError, "validate_slength(): Expected second argument to be equal to or larger than third argument" unless max_length >= min_length + + validator = lambda do |str| + unless str.length <= max_length and str.length >= min_length + raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}" + end + end + + case input + when String + validator.call(input) + when Array + input.each_with_index do |arg, pos| + if arg.is_a? String + validator.call(arg) + else + raise Puppet::ParseError, "validate_slength(): Expected element at array position #{pos} to be a String, got #{arg.class}" + end + end + else + raise Puppet::ParseError, "validate_slength(): Expected first argument to be a String or Array, got #{input.class}" + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_string.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_string.rb new file mode 100644 index 0000000..6675d86 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_string.rb @@ -0,0 +1,41 @@ +module Puppet::Parser::Functions + + newfunction(:validate_string, :doc => <<-'ENDHEREDOC') do |args| + Validate that all passed values are string data structures. Abort catalog + compilation if any value fails this check. + + The following values will pass: + + $my_string = "one two" + validate_string($my_string, 'three') + + The following values will fail, causing compilation to abort: + + validate_string(true) + validate_string([ 'some', 'array' ]) + + Note: validate_string(undef) will not fail in this version of the + functions API (incl. current and future parser). Instead, use: + + if $var == undef { + fail('...') + } + + ENDHEREDOC + + function_deprecation([:validate_string, 'This method is deprecated, please use the stdlib validate_legacy function, with Stdlib::Compat::String. There is further documentation for validate_legacy function in the README.']) + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_string(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + # when called through the v4 API shim, undef gets translated to nil + unless arg.is_a?(String) || arg.nil? + raise Puppet::ParseError, ("#{arg.inspect} is not a string. It looks to be a #{arg.class}") + end + end + + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb new file mode 100644 index 0000000..fc9f23f --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb @@ -0,0 +1,47 @@ +module Puppet::Parser::Functions + + newfunction(:validate_x509_rsa_key_pair, :doc => <<-ENDHEREDOC + Validates a PEM-formatted X.509 certificate and RSA private key using + OpenSSL. Verifies that the certficate's signature was created from the + supplied key. + + Fail compilation if any value fails this check. + + validate_x509_rsa_key_pair($cert, $key) + + ENDHEREDOC + ) do |args| + + require 'openssl' + + NUM_ARGS = 2 unless defined? NUM_ARGS + + unless args.length == NUM_ARGS then + raise Puppet::ParseError, + ("validate_x509_rsa_key_pair(): wrong number of arguments (#{args.length}; must be #{NUM_ARGS})") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + end + + begin + cert = OpenSSL::X509::Certificate.new(args[0]) + rescue OpenSSL::X509::CertificateError => e + raise Puppet::ParseError, "Not a valid x509 certificate: #{e}" + end + + begin + key = OpenSSL::PKey::RSA.new(args[1]) + rescue OpenSSL::PKey::RSAError => e + raise Puppet::ParseError, "Not a valid RSA key: #{e}" + end + + unless cert.verify(key) + raise Puppet::ParseError, "Certificate signature does not match supplied key" + end + end + +end diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/values.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/values.rb new file mode 100644 index 0000000..0ca236c --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/values.rb @@ -0,0 +1,38 @@ +# +# values.rb +# + +module Puppet::Parser::Functions + newfunction(:values, :type => :rvalue, :doc => <<-EOS +When given a hash this function will return the values of that hash. + +*Examples:* + + $hash = { + 'a' => 1, + 'b' => 2, + 'c' => 3, + } + values($hash) + +This example would return: + + [1,2,3] + EOS + ) do |arguments| + + raise(Puppet::ParseError, "values(): Wrong number of arguments given (#{arguments.size} for 1)") if arguments.size < 1 + + hash = arguments[0] + + unless hash.is_a?(Hash) + raise(Puppet::ParseError, 'values(): Requires hash to work with') + end + + result = hash.values + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/values_at.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/values_at.rb new file mode 100644 index 0000000..04a3d1a --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/values_at.rb @@ -0,0 +1,93 @@ +# +# values_at.rb +# + +module Puppet::Parser::Functions + newfunction(:values_at, :type => :rvalue, :doc => <<-EOS +Finds value inside an array based on location. + +The first argument is the array you want to analyze, and the second element can +be a combination of: + +* A single numeric index +* A range in the form of 'start-stop' (eg. 4-9) +* An array combining the above + +*Examples*: + + values_at(['a','b','c'], 2) + +Would return ['c']. + + values_at(['a','b','c'], ["0-1"]) + +Would return ['a','b']. + + values_at(['a','b','c','d','e'], [0, "2-3"]) + +Would return ['a','c','d']. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "values_at(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + array = arguments.shift + + unless array.is_a?(Array) + raise(Puppet::ParseError, 'values_at(): Requires array to work with') + end + + indices = [arguments.shift].flatten() # Get them all ... Pokemon ... + + if not indices or indices.empty? + raise(Puppet::ParseError, 'values_at(): You must provide at least one positive index to collect') + end + + result = [] + indices_list = [] + + indices.each do |i| + i = i.to_s + if m = i.match(/^(\d+)(\.\.\.?|\-)(\d+)$/) + start = m[1].to_i + stop = m[3].to_i + + type = m[2] + + if start > stop + raise(Puppet::ParseError, 'values_at(): Stop index in given indices range is smaller than the start index') + elsif stop > array.size - 1 # First element is at index 0 is it not? + raise(Puppet::ParseError, 'values_at(): Stop index in given indices range exceeds array size') + end + + range = case type + when /^(\.\.|\-)$/ then (start .. stop) + when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ... + end + + range.each { |i| indices_list << i.to_i } + else + # Only positive numbers allowed in this case ... + if not i.match(/^\d+$/) + raise(Puppet::ParseError, 'values_at(): Unknown format of given index') + end + + # In Puppet numbers are often string-encoded ... + i = i.to_i + + if i > array.size - 1 # Same story. First element is at index 0 ... + raise(Puppet::ParseError, 'values_at(): Given index exceeds array size') + end + + indices_list << i + end + end + + # We remove nil values as they make no sense in Puppet DSL ... + result = indices_list.collect { |i| array[i] }.compact + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/parser/functions/zip.rb b/environments/production/modules/stdlib/lib/puppet/parser/functions/zip.rb new file mode 100644 index 0000000..13e24b6 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/parser/functions/zip.rb @@ -0,0 +1,38 @@ +# +# zip.rb +# + +module Puppet::Parser::Functions + newfunction(:zip, :type => :rvalue, :doc => <<-EOS +Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments. + +*Example:* + + zip(['1','2','3'],['4','5','6']) + +Would result in: + + ["1", "4"], ["2", "5"], ["3", "6"] + EOS + ) do |arguments| + + # Technically we support three arguments but only first is mandatory ... + raise(Puppet::ParseError, "zip(): Wrong number of arguments given (#{arguments.size} for 2)") if arguments.size < 2 + + a = arguments[0] + b = arguments[1] + + unless a.is_a?(Array) and b.is_a?(Array) + raise(Puppet::ParseError, 'zip(): Requires array to work with') + end + + flatten = function_str2bool([arguments[2]]) if arguments[2] + + result = a.zip(b) + result = flatten ? result.flatten : result + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/environments/production/modules/stdlib/lib/puppet/provider/file_line/ruby.rb b/environments/production/modules/stdlib/lib/puppet/provider/file_line/ruby.rb new file mode 100644 index 0000000..fbf7d8e --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/provider/file_line/ruby.rb @@ -0,0 +1,132 @@ +Puppet::Type.type(:file_line).provide(:ruby) do + def exists? + if resource[:replace].to_s != 'true' and count_matches(match_regex) > 0 + true + else + lines.find do |line| + if resource[:ensure].to_s == 'absent' and resource[:match_for_absence].to_s == 'true' + line.chomp =~ Regexp.new(resource[:match]) + else + line.chomp == resource[:line].chomp + end + end + end + end + + def create + unless resource[:replace].to_s != 'true' and count_matches(match_regex) > 0 + if resource[:match] + handle_create_with_match + elsif resource[:after] + handle_create_with_after + else + append_line + end + end + end + + def destroy + if resource[:match_for_absence].to_s == 'true' and resource[:match] + handle_destroy_with_match + else + handle_destroy_line + end + end + + private + def lines + # If this type is ever used with very large files, we should + # write this in a different way, using a temp + # file; for now assuming that this type is only used on + # small-ish config files that can fit into memory without + # too much trouble. + @lines ||= File.readlines(resource[:path], :encoding => resource[:encoding]) + end + + def match_regex + resource[:match] ? Regexp.new(resource[:match]) : nil + end + + def handle_create_with_match() + regex_after = resource[:after] ? Regexp.new(resource[:after]) : nil + match_count = count_matches(match_regex) + + if match_count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + end + + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(match_regex.match(l) ? resource[:line] : l) + if (match_count == 0 and regex_after) + if regex_after.match(l) + fh.puts(resource[:line]) + match_count += 1 #Increment match_count to indicate that the new line has been inserted. + end + end + end + + if (match_count == 0) + fh.puts(resource[:line]) + end + end + end + + def handle_create_with_after + regex = Regexp.new(resource[:after]) + count = count_matches(regex) + + if count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'. One or no line must match the pattern." + end + + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(l) + if regex.match(l) then + fh.puts(resource[:line]) + end + end + end + + if (count == 0) # append the line to the end of the file + append_line + end + end + + def count_matches(regex) + lines.select{|l| l.match(regex)}.size + end + + def handle_destroy_with_match + match_count = count_matches(match_regex) + if match_count > 1 && resource[:multiple].to_s != 'true' + raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + end + + local_lines = lines + File.open(resource[:path],'w') do |fh| + fh.write(local_lines.reject{|l| match_regex.match(l) }.join('')) + end + end + + def handle_destroy_line + local_lines = lines + File.open(resource[:path],'w') do |fh| + fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join('')) + end + end + + ## + # append the line to the file. + # + # @api private + def append_line + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(l) + end + fh.puts resource[:line] + end + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/type/anchor.rb b/environments/production/modules/stdlib/lib/puppet/type/anchor.rb new file mode 100644 index 0000000..fe1e5aa --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/type/anchor.rb @@ -0,0 +1,46 @@ +Puppet::Type.newtype(:anchor) do + desc <<-'ENDOFDESC' + A simple resource type intended to be used as an anchor in a composite class. + + In Puppet 2.6, when a class declares another class, the resources in the + interior class are not contained by the exterior class. This interacts badly + with the pattern of composing complex modules from smaller classes, as it + makes it impossible for end users to specify order relationships between the + exterior class and other modules. + + The anchor type lets you work around this. By sandwiching any interior + classes between two no-op resources that _are_ contained by the exterior + class, you can ensure that all resources in the module are contained. + + class ntp { + # These classes will have the correct order relationship with each + # other. However, without anchors, they won't have any order + # relationship to Class['ntp']. + class { 'ntp::package': } + -> class { 'ntp::config': } + -> class { 'ntp::service': } + + # These two resources "anchor" the composed classes within the ntp + # class. + anchor { 'ntp::begin': } -> Class['ntp::package'] + Class['ntp::service'] -> anchor { 'ntp::end': } + } + + This allows the end user of the ntp module to establish require and before + relationships with Class['ntp']: + + class { 'ntp': } -> class { 'mcollective': } + class { 'mcollective': } -> class { 'ntp': } + + ENDOFDESC + + newparam :name do + desc "The name of the anchor resource." + end + + def refresh + # We don't do anything with them, but we need this to + # show that we are "refresh aware" and not break the + # chain of propagation. + end +end diff --git a/environments/production/modules/stdlib/lib/puppet/type/file_line.rb b/environments/production/modules/stdlib/lib/puppet/type/file_line.rb new file mode 100644 index 0000000..e82b246 --- /dev/null +++ b/environments/production/modules/stdlib/lib/puppet/type/file_line.rb @@ -0,0 +1,146 @@ +Puppet::Type.newtype(:file_line) do + + desc <<-EOT + Ensures that a given line is contained within a file. The implementation + matches the full line, including whitespace at the beginning and end. If + the line is not contained in the given file, Puppet will append the line to + the end of the file to ensure the desired state. Multiple resources may + be declared to manage multiple lines in the same file. + + Example: + + file_line { 'sudo_rule': + path => '/etc/sudoers', + line => '%sudo ALL=(ALL) ALL', + } + + file_line { 'sudo_rule_nopw': + path => '/etc/sudoers', + line => '%sudonopw ALL=(ALL) NOPASSWD: ALL', + } + + In this example, Puppet will ensure both of the specified lines are + contained in the file /etc/sudoers. + + Match Example: + + file_line { 'bashrc_proxy': + ensure => present, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + } + + In this code example match will look for a line beginning with export + followed by HTTP_PROXY and replace it with the value in line. + + Match Example With `ensure => absent`: + + file_line { 'bashrc_proxy': + ensure => absent, + path => '/etc/bashrc', + line => 'export HTTP_PROXY=http://squid.puppetlabs.vm:3128', + match => '^export\ HTTP_PROXY\=', + match_for_absence => true, + } + + In this code example match will look for a line beginning with export + followed by HTTP_PROXY and delete it. If multiple lines match, an + error will be raised unless the `multiple => true` parameter is set. + + Encoding example: + + file_line { "XScreenSaver": + ensure => present, + path => '/root/XScreenSaver' + line => "*lock: 10:00:00", + match => '^*lock:', + encoding => "iso-8859-1", + } + + Files with special characters that are not valid UTF-8 will give the + error message "invalid byte sequence in UTF-8". In this case, determine + the correct file encoding and specify the correct encoding using the + encoding attribute, the value of which needs to be a valid Ruby character + encoding. + + **Autorequires:** If Puppet is managing the file that will contain the line + being managed, the file_line resource will autorequire that file. + EOT + + ensurable do + defaultvalues + defaultto :present + end + + newparam(:name, :namevar => true) do + desc 'An arbitrary name used as the identity of the resource.' + end + + newparam(:match) do + desc 'An optional ruby regular expression to run against existing lines in the file.' + + ' If a match is found, we replace that line rather than adding a new line.' + + ' A regex comparison is performed against the line value and if it does not' + + ' match an exception will be raised.' + end + + newparam(:match_for_absence) do + desc 'An optional value to determine if match should be applied when ensure => absent.' + + ' If set to true and match is set, the line that matches match will be deleted.' + + ' If set to false (the default), match is ignored when ensure => absent.' + + ' When `ensure => present`, match_for_absence is ignored.' + newvalues(true, false) + defaultto false + end + + newparam(:multiple) do + desc 'An optional value to determine if match can change multiple lines.' + + ' If set to false, an exception will be raised if more than one line matches' + newvalues(true, false) + end + + newparam(:after) do + desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)' + + ' This is also takes a regex.' + end + + newparam(:line) do + desc 'The line to be appended to the file or used to replace matches found by the match attribute.' + end + + newparam(:path) do + desc 'The file Puppet will ensure contains the line specified by the line parameter.' + validate do |value| + unless Puppet::Util.absolute_path?(value) + raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" + end + end + end + + newparam(:replace) do + desc 'If true, replace line that matches. If false, do not write line if a match is found' + newvalues(true, false) + defaultto true + end + + newparam(:encoding) do + desc 'For files that are not UTF-8 encoded, specify encoding such as iso-8859-1' + defaultto 'UTF-8' + end + + # Autorequire the file resource if it's being managed + autorequire(:file) do + self[:path] + end + + validate do + unless self[:line] + unless (self[:ensure].to_s == 'absent') and (self[:match_for_absence].to_s == 'true') and self[:match] + raise(Puppet::Error, "line is a required attribute") + end + end + unless self[:path] + raise(Puppet::Error, "path is a required attribute") + end + end +end diff --git a/environments/production/modules/stdlib/manifests/init.pp b/environments/production/modules/stdlib/manifests/init.pp new file mode 100644 index 0000000..9ea22a7 --- /dev/null +++ b/environments/production/modules/stdlib/manifests/init.pp @@ -0,0 +1,18 @@ +# Class: stdlib +# +# This module manages stdlib. Most of stdlib's features are automatically +# loaded by Puppet, but this class should be declared in order to use the +# standardized run stages. +# +# Parameters: none +# +# Actions: +# +# Declares all other classes in the stdlib module. Currently, this consists +# of stdlib::stages. +# +# Requires: nothing +# +class stdlib { + include ::stdlib::stages +} diff --git a/environments/production/modules/stdlib/manifests/stages.pp b/environments/production/modules/stdlib/manifests/stages.pp new file mode 100644 index 0000000..7de254c --- /dev/null +++ b/environments/production/modules/stdlib/manifests/stages.pp @@ -0,0 +1,43 @@ +# Class: stdlib::stages +# +# This class manages a standard set of run stages for Puppet. It is managed by +# the stdlib class, and should not be declared independently. +# +# The high level stages are (in order): +# +# * setup +# * main +# * runtime +# * setup_infra +# * deploy_infra +# * setup_app +# * deploy_app +# * deploy +# +# Parameters: none +# +# Actions: +# +# Declares various run-stages for deploying infrastructure, +# language runtimes, and application layers. +# +# Requires: nothing +# +# Sample Usage: +# +# node default { +# include ::stdlib +# class { java: stage => 'runtime' } +# } +# +class stdlib::stages { + + stage { 'setup': before => Stage['main'] } + stage { 'runtime': require => Stage['main'] } + -> stage { 'setup_infra': } + -> stage { 'deploy_infra': } + -> stage { 'setup_app': } + -> stage { 'deploy_app': } + -> stage { 'deploy': } + +} diff --git a/environments/production/modules/stdlib/metadata.json b/environments/production/modules/stdlib/metadata.json new file mode 100644 index 0000000..419a8af --- /dev/null +++ b/environments/production/modules/stdlib/metadata.json @@ -0,0 +1,112 @@ +{ + "name": "puppetlabs-stdlib", + "version": "4.16.0", + "author": "puppetlabs", + "summary": "Standard library of resources for Puppet modules.", + "license": "Apache-2.0", + "source": "https://github.com/puppetlabs/puppetlabs-stdlib", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "dependencies": [ + + ], + "data_provider": null, + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "Scientific", + "operatingsystemrelease": [ + "4", + "5", + "6", + "7" + ] + }, + { + "operatingsystem": "SLES", + "operatingsystemrelease": [ + "10 SP4", + "11 SP1", + "12" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7", + "8" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "10.04", + "12.04", + "14.04", + "16.04" + ] + }, + { + "operatingsystem": "Solaris", + "operatingsystemrelease": [ + "10", + "11" + ] + }, + { + "operatingsystem": "Windows", + "operatingsystemrelease": [ + "Server 2003", + "Server 2003 R2", + "Server 2008", + "Server 2008 R2", + "Server 2012", + "Server 2012 R2", + "7", + "8" + ] + }, + { + "operatingsystem": "AIX", + "operatingsystemrelease": [ + "5.3", + "6.1", + "7.1" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">=2.7.20 <5.0.0" + } + ], + "description": "Standard Library for Puppet Modules" +} diff --git a/environments/production/modules/stdlib/spec/acceptance/abs_spec.rb b/environments/production/modules/stdlib/spec/acceptance/abs_spec.rb new file mode 100755 index 0000000..5af436f --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/abs_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'abs function' do + describe 'success' do + it 'should accept a string' do + pp = <<-EOS + $input = '-34.56' + $output = abs($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 34.56/) + end + end + + it 'should accept a float' do + pp = <<-EOS + $input = -34.56 + $output = abs($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 34.56/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/anchor_spec.rb b/environments/production/modules/stdlib/spec/acceptance/anchor_spec.rb new file mode 100755 index 0000000..24a9064 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/anchor_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper_acceptance' + +describe 'anchor type' do + describe 'success' do + it 'should effect proper chaining of resources' do + pp = <<-EOS + class anchored { + anchor { 'anchored::begin': } + ~> anchor { 'anchored::end': } + } + + class anchorrefresh { + notify { 'first': } + ~> class { 'anchored': } + ~> anchor { 'final': } + } + + include anchorrefresh + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Anchor\[final\]: Triggered 'refresh'/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/any2array_spec.rb b/environments/production/modules/stdlib/spec/acceptance/any2array_spec.rb new file mode 100755 index 0000000..8a13911 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/any2array_spec.rb @@ -0,0 +1,49 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'any2array function' do + describe 'success' do + it 'should create an empty array' do + pp = <<-EOS + $input = '' + $output = any2array($input) + validate_array($output) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: /) + end + end + + it 'should leave arrays modified' do + pp = <<-EOS + $input = ['test', 'array'] + $output = any2array($input) + validate_array($output) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/) + end + end + + it 'should turn a hash into an array' do + pp = <<-EOS + $input = {'test' => 'array'} + $output = any2array($input) + + validate_array($output) + # Check each element of the array is a plain string. + validate_string($output[0]) + validate_string($output[1]) + notify { "Output: ${output}": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/base64_spec.rb b/environments/production/modules/stdlib/spec/acceptance/base64_spec.rb new file mode 100755 index 0000000..e9096a7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/base64_spec.rb @@ -0,0 +1,18 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'base64 function' do + describe 'success' do + it 'should encode then decode a string' do + pp = <<-EOS + $encodestring = base64('encode', 'thestring') + $decodestring = base64('decode', $encodestring) + notify { $decodestring: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/thestring/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/bool2num_spec.rb b/environments/production/modules/stdlib/spec/acceptance/bool2num_spec.rb new file mode 100755 index 0000000..c69acc6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/bool2num_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'bool2num function' do + describe 'success' do + ['false', 'f', '0', 'n', 'no'].each do |bool| + it "should convert a given boolean, #{bool}, to 0" do + pp = <<-EOS + $input = "#{bool}" + $output = bool2num($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 0/) + end + end + end + + ['true', 't', '1', 'y', 'yes'].each do |bool| + it "should convert a given boolean, #{bool}, to 1" do + pp = <<-EOS + $input = "#{bool}" + $output = bool2num($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 1/) + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/build_csv.rb b/environments/production/modules/stdlib/spec/acceptance/build_csv.rb new file mode 100755 index 0000000..62ecbf1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/build_csv.rb @@ -0,0 +1,83 @@ +#!/usr/bin/env ruby +# vim: set sw=2 sts=2 et tw=80 : +require 'rspec' + +#XXX Super ugly hack to keep from starting beaker nodes +module Kernel + # make an alias of the original require + alias_method :original_require, :require + # rewrite require + def require name + original_require name if name != 'spec_helper_acceptance' + end +end +UNSUPPORTED_PLATFORMS = [] +def fact(*args) [] end +#XXX End hax + +# Get a list of functions for test coverage +function_list = Dir[File.join(File.dirname(__FILE__),"..","..","lib","puppet","parser","functions","*.rb")].collect do |function_rb| + File.basename(function_rb,".rb") +end + +## Configure rspec to parse tests +options = RSpec::Core::ConfigurationOptions.new(['spec/acceptance']) +configuration = RSpec::configuration +world = RSpec::world +options.parse_options +options.configure(configuration) +configuration.load_spec_files + +## Collect up tests and example groups into a hash +def get_tests(children) + children.inject({}) do |memo,c| + memo[c.description] = Hash.new + memo[c.description]["groups"] = get_tests(c.children) unless c.children.empty? + memo[c.description]["tests"] = c.examples.collect { |e| + e.description unless e.pending? + }.compact unless c.examples.empty? + memo[c.description]["pending_tests"] = c.examples.collect { |e| + e.description if e.pending? + }.compact unless c.examples.empty? + memo + end +end + +def count_test_types_in(type,group) + return 0 if group.nil? + group.inject(0) do |m,(k,v)| + m += v.length if k == type + m += count_tests_in(v) if v.is_a?(Hash) + m + end +end +def count_tests_in(group) + count_test_types_in('tests',group) +end +def count_pending_tests_in(group) + count_test_types_in('pending_tests',group) +end + +# Convert tests hash to csv format +def to_csv(function_list,tests) + function_list.collect do |function_name| + if v = tests["#{function_name} function"] + positive_tests = count_tests_in(v["groups"]["success"]) + negative_tests = count_tests_in(v["groups"]["failure"]) + pending_tests = + count_pending_tests_in(v["groups"]["failure"]) + + count_pending_tests_in(v["groups"]["failure"]) + else + positive_tests = 0 + negative_tests = 0 + pending_tests = 0 + end + sprintf("%-25s, %-9d, %-9d, %-9d", function_name,positive_tests,negative_tests,pending_tests) + end.compact +end + +tests = get_tests(world.example_groups) +csv = to_csv(function_list,tests) +percentage_tested = "#{tests.count*100/function_list.count}%" +printf("%-25s, %-9s, %-9s, %-9s\n","#{percentage_tested} have tests.","Positive","Negative","Pending") +puts csv diff --git a/environments/production/modules/stdlib/spec/acceptance/capitalize_spec.rb b/environments/production/modules/stdlib/spec/acceptance/capitalize_spec.rb new file mode 100755 index 0000000..03d01a8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/capitalize_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'capitalize function' do + describe 'success' do + it 'should capitalize the first letter of a string' do + pp = <<-EOS + $input = 'this is a string' + $output = capitalize($input) + notify { $output: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: This is a string/) + end + end + + it 'should capitalize the first letter of an array of strings' do + pp = <<-EOS + $input = ['this', 'is', 'a', 'string'] + $output = capitalize($input) + notify { $output: } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: This/) + expect(r.stdout).to match(/Notice: Is/) + expect(r.stdout).to match(/Notice: A/) + expect(r.stdout).to match(/Notice: String/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/ceiling_spec.rb b/environments/production/modules/stdlib/spec/acceptance/ceiling_spec.rb new file mode 100755 index 0000000..895e4a0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/ceiling_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'ceiling function' do + describe 'success' do + it 'ceilings floats' do + pp = <<-EOS + $a = 12.8 + $b = 13 + $o = ceiling($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'ceilings integers' do + pp = <<-EOS + $a = 7 + $b = 7 + $o = ceiling($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/chomp_spec.rb b/environments/production/modules/stdlib/spec/acceptance/chomp_spec.rb new file mode 100755 index 0000000..56e0876 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/chomp_spec.rb @@ -0,0 +1,21 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'chomp function' do + describe 'success' do + it 'should eat the newline' do + pp = <<-EOS + $input = "test\n" + if size($input) != 5 { + fail("Size of ${input} is not 5.") + } + $output = chomp($input) + if size($output) != 4 { + fail("Size of ${input} is not 4.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/chop_spec.rb b/environments/production/modules/stdlib/spec/acceptance/chop_spec.rb new file mode 100755 index 0000000..0993806 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/chop_spec.rb @@ -0,0 +1,45 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'chop function' do + describe 'success' do + it 'should eat the last character' do + pp = <<-EOS + $input = "test" + if size($input) != 4 { + fail("Size of ${input} is not 4.") + } + $output = chop($input) + if size($output) != 3 { + fail("Size of ${input} is not 3.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should eat the last two characters of \r\n' do + pp = <<-'EOS' + $input = "test\r\n" + if size($input) != 6 { + fail("Size of ${input} is not 6.") + } + $output = chop($input) + if size($output) != 4 { + fail("Size of ${input} is not 4.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + it 'should not fail on empty strings' do + pp = <<-EOS + $input = "" + $output = chop($input) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/clamp_spec.rb b/environments/production/modules/stdlib/spec/acceptance/clamp_spec.rb new file mode 100755 index 0000000..e8ccb96 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/clamp_spec.rb @@ -0,0 +1,40 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'clamp function' do + describe 'success' do + it 'clamps list of values' do + pp = <<-EOS + $x = 17 + $y = 225 + $z = 155 + $o = clamp($x, $y, $z) + if $o == $z { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'clamps array of values' do + pp = <<-EOS + $a = [7, 19, 66] + $b = 19 + $o = clamp($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/concat_spec.rb b/environments/production/modules/stdlib/spec/acceptance/concat_spec.rb new file mode 100755 index 0000000..8d184d1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/concat_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'concat function' do + describe 'success' do + it 'should concat one array to another' do + pp = <<-EOS + $output = concat(['1','2','3'],['4','5','6']) + validate_array($output) + if size($output) != 6 { + fail("${output} should have 6 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat arrays and primitives to array' do + pp = <<-EOS + $output = concat(['1','2','3'],'4','5','6',['7','8','9']) + validate_array($output) + if size($output) != 9 { + fail("${output} should have 9 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat multiple arrays to one' do + pp = <<-EOS + $output = concat(['1','2','3'],['4','5','6'],['7','8','9']) + validate_array($output) + if size($output) != 9 { + fail("${output} should have 9 elements.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'should concat hash arguments' do + pp = <<-EOS + $output = concat([{"a" => "b"}], {"c" => "d", "e" => "f"}) + validate_array($output) + if size($output) != 2 { + fail("${output} should have 2 elements.") + } + if $output[1] != {"c" => "d", "e" => "f"} { + fail("${output} does not have the expected hash for the second element.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/count_spec.rb b/environments/production/modules/stdlib/spec/acceptance/count_spec.rb new file mode 100755 index 0000000..18c039d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/count_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'count function' do + describe 'success' do + it 'should count elements in an array' do + pp = <<-EOS + $input = [1,2,3,4] + $output = count($input) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 4/) + end + end + + it 'should count elements in an array that match a second argument' do + pp = <<-EOS + $input = [1,1,1,2] + $output = count($input, 1) + notify { "$output": } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: 3/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/deep_merge_spec.rb b/environments/production/modules/stdlib/spec/acceptance/deep_merge_spec.rb new file mode 100755 index 0000000..8222f24 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/deep_merge_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'deep_merge function' do + describe 'success' do + it 'should deep merge two hashes' do + pp = <<-EOS + $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } + $merged_hash = deep_merge($hash1, $hash2) + + if $merged_hash != { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } { + fail("Hash was incorrectly merged.") + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/defined_with_params_spec.rb b/environments/production/modules/stdlib/spec/acceptance/defined_with_params_spec.rb new file mode 100755 index 0000000..a332bd6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/defined_with_params_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'defined_with_params function' do + describe 'success' do + it 'should successfully notify' do + pp = <<-EOS + user { 'dan': + ensure => present, + } + + if defined_with_params(User[dan], {'ensure' => 'present' }) { + notify { 'User defined with ensure=>present': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: User defined with ensure=>present/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/delete_at_spec.rb b/environments/production/modules/stdlib/spec/acceptance/delete_at_spec.rb new file mode 100755 index 0000000..d4f852a --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/delete_at_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_at function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete_at(['a','b','c','b'], 1) + if $output == ['a','c','b'] { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/delete_spec.rb b/environments/production/modules/stdlib/spec/acceptance/delete_spec.rb new file mode 100755 index 0000000..f85b093 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/delete_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete(['a','b','c','b'], 'b') + if $output == ['a','c'] { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb b/environments/production/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb new file mode 100755 index 0000000..af45a92 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_undef_values function' do + describe 'success' do + it 'should delete elements of the array' do + pp = <<-EOS + $output = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + if $output == { a => 'A', b => '', d => false } { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/delete_values_spec.rb b/environments/production/modules/stdlib/spec/acceptance/delete_values_spec.rb new file mode 100755 index 0000000..04b6920 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/delete_values_spec.rb @@ -0,0 +1,25 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'delete_values function' do + describe 'success' do + it 'should delete elements of the hash' do + pp = <<-EOS + $a = { 'a' => 'A', 'b' => 'B', 'B' => 'C', 'd' => 'B' } + $b = { 'a' => 'A', 'B' => 'C' } + $o = delete_values($a, 'B') + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles non-hash arguments' + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/deprecation_spec.rb b/environments/production/modules/stdlib/spec/acceptance/deprecation_spec.rb new file mode 100644 index 0000000..7a0b34c --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/deprecation_spec.rb @@ -0,0 +1,102 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'deprecation function' do + + if fact('operatingsystem') == 'windows' + test_file = 'C:/deprecation' + else + test_file = "/tmp/deprecation" + end + + # It seems that Windows needs everything to be on one line when using puppet apply -e, otherwise the manifests would be in an easier format + add_file_manifest = "\"deprecation('key', 'message') file { '#{test_file}': ensure => present, content => 'test', }\"" + remove_file_manifest = "file { '#{test_file}': ensure => absent }" + + before :all do + apply_manifest(remove_file_manifest) + end + + context 'with --strict=error', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=error', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should return an error" do + expect(@result.exit_code).to eq(1) + end + + it "should show the error message" do + expect(@result.stderr).to match(/deprecation. key. message/) + end + + describe file("#{test_file}") do + it { is_expected.not_to be_file } + end + end + + context 'with --strict=warning', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=warning', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should not return an error" do + expect(@result.exit_code).to eq(0) + end + + it "should show the error message" do + expect(@result.stderr).to match(/Warning: message/) + end + + describe file("#{test_file}") do + it { is_expected.to be_file } + end + end + + context 'with --strict=off', if: get_puppet_version =~ /^4/ do + before :all do + @result = on(default, puppet('apply', '--strict=off', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + + after :all do + apply_manifest(remove_file_manifest) + end + + it "should not return an error" do + expect(@result.exit_code).to eq(0) + end + + it "should not show the error message" do + expect(@result.stderr).not_to match(/Warning: message/) + end + + describe file("#{test_file}") do + it { is_expected.to be_file } + end + end + + context 'puppet 3 test', if: get_puppet_version =~ /^3/ do + before :all do + @result = on(default, puppet('apply', '--parser=future', '-e', add_file_manifest), acceptable_exit_codes: (0...256)) + end + after :all do + apply_manifest(remove_file_manifest) + end + + it "should return a deprecation error" do + expect(@result.stderr).to match(/Warning: message/) + end + it "should pass without error" do + expect(@result.exit_code).to eq(0) + end + end + +end diff --git a/environments/production/modules/stdlib/spec/acceptance/difference_spec.rb b/environments/production/modules/stdlib/spec/acceptance/difference_spec.rb new file mode 100755 index 0000000..68f6bbe --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/difference_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'difference function' do + describe 'success' do + it 'returns non-duplicates in the first array' do + pp = <<-EOS + $a = ['a','b','c'] + $b = ['b','c','d'] + $c = ['a'] + $o = difference($a, $b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles non-array arguments' + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/dirname_spec.rb b/environments/production/modules/stdlib/spec/acceptance/dirname_spec.rb new file mode 100755 index 0000000..db83f0f --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/dirname_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'dirname function' do + describe 'success' do + context 'absolute path' do + it 'returns the dirname' do + pp = <<-EOS + $a = '/path/to/a/file.txt' + $b = '/path/to/a' + $o = dirname($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + context 'relative path' do + it 'returns the dirname' do + pp = <<-EOS + $a = 'path/to/a/file.txt' + $b = 'path/to/a' + $o = dirname($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/downcase_spec.rb b/environments/production/modules/stdlib/spec/acceptance/downcase_spec.rb new file mode 100755 index 0000000..300bcfa --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/downcase_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'downcase function' do + describe 'success' do + it 'returns the downcase' do + pp = <<-EOS + $a = 'AOEU' + $b = 'aoeu' + $o = downcase($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'doesn\'t affect lowercase words' do + pp = <<-EOS + $a = 'aoeu aoeu' + $b = 'aoeu aoeu' + $o = downcase($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/empty_spec.rb b/environments/production/modules/stdlib/spec/acceptance/empty_spec.rb new file mode 100755 index 0000000..97b7333 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/empty_spec.rb @@ -0,0 +1,53 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'empty function' do + describe 'success' do + it 'recognizes empty strings' do + pp = <<-EOS + $a = '' + $b = true + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'recognizes non-empty strings' do + pp = <<-EOS + $a = 'aoeu' + $b = false + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles numerical values' do + pp = <<-EOS + $a = 7 + $b = false + $o = empty($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/ensure_resource_spec.rb b/environments/production/modules/stdlib/spec/acceptance/ensure_resource_spec.rb new file mode 100755 index 0000000..93f25dd --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/ensure_resource_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'ensure_resource function' do + describe 'success' do + it 'ensures a resource already declared' do + apply_manifest('') + pp = <<-EOS + notify { "test": loglevel => 'err' } + ensure_resource('notify', 'test', { 'loglevel' => 'err' }) + EOS + + apply_manifest(pp, :expect_changes => true) + end + + it 'ensures a undeclared resource' do + apply_manifest('') + pp = <<-EOS + ensure_resource('notify', 'test', { 'loglevel' => 'err' }) + EOS + + apply_manifest(pp, :expect_changes => true) + end + it 'takes defaults arguments' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/flatten_spec.rb b/environments/production/modules/stdlib/spec/acceptance/flatten_spec.rb new file mode 100755 index 0000000..289eec9 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/flatten_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'flatten function' do + describe 'success' do + it 'flattens arrays' do + pp = <<-EOS + $a = ["a","b",["c",["d","e"],"f","g"]] + $b = ["a","b","c","d","e","f","g"] + $o = flatten($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'does not affect flat arrays' do + pp = <<-EOS + $a = ["a","b","c","d","e","f","g"] + $b = ["a","b","c","d","e","f","g"] + $o = flatten($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/floor_spec.rb b/environments/production/modules/stdlib/spec/acceptance/floor_spec.rb new file mode 100755 index 0000000..8259d2a --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/floor_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'floor function' do + describe 'success' do + it 'floors floats' do + pp = <<-EOS + $a = 12.8 + $b = 12 + $o = floor($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'floors integers' do + pp = <<-EOS + $a = 7 + $b = 7 + $o = floor($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb b/environments/production/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb new file mode 100644 index 0000000..af1b2a9 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/fqdn_rand_string_spec.rb @@ -0,0 +1,66 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'fqdn_rand_string function' do + describe 'success' do + include_context "with faked facts" + context "when the FQDN is 'fakehost.localdomain'" do + before :each do + fake_fact("fqdn", "fakehost.localdomain") + end + + it 'generates random alphanumeric strings' do + pp = <<-eos + $l = 10 + $o = fqdn_rand_string($l) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(7oDp0KOr1b|9Acvnhkt4J)"/) + end + end + it 'generates random alphanumeric strings with custom charsets' do + pp = <<-eos + $l = 10 + $c = '0123456789' + $o = fqdn_rand_string($l, $c) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(7203048515|2383756694)"/) + end + end + it 'generates random alphanumeric strings with custom seeds' do + pp = <<-eos + $l = 10 + $s = 'seed' + $o = fqdn_rand_string($l, undef, $s) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(3HS4mbuI3E|1jJtAMs94d)"/) + end + end + it 'generates random alphanumeric strings with custom charsets and seeds' do + pp = <<-eos + $l = 10 + $c = '0123456789' + $s = 'seed' + $o = fqdn_rand_string($l, $c, $s) + notice(inline_template('fqdn_rand_string is <%= @o.inspect %>')) + eos + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rand_string is "(3104058232|7100592312)"/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers for length argument' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb b/environments/production/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb new file mode 100755 index 0000000..66e94a9 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb @@ -0,0 +1,64 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'fqdn_rotate function' do + describe 'success' do + include_context "with faked facts" + context "when the FQDN is 'fakehost.localdomain'" do + before :each do + fake_fact("fqdn", "fakehost.localdomain") + end + + it 'rotates arrays' do + pp = <<-EOS + $a = ['a','b','c','d'] + $o = fqdn_rotate($a) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is \["d", "a", "b", "c"\]/) + end + end + it 'rotates arrays with custom seeds' do + pp = <<-EOS + $a = ['a','b','c','d'] + $s = 'seed' + $o = fqdn_rotate($a, $s) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is \["c", "d", "a", "b"\]/) + end + end + it 'rotates strings' do + pp = <<-EOS + $a = 'abcd' + $o = fqdn_rotate($a) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is "dabc"/) + end + end + it 'rotates strings with custom seeds' do + pp = <<-EOS + $a = 'abcd' + $s = 'seed' + $o = fqdn_rotate($a, $s) + notice(inline_template('fqdn_rotate is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/fqdn_rotate is "cdab"/) + end + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles invalid arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/get_module_path_spec.rb b/environments/production/modules/stdlib/spec/acceptance/get_module_path_spec.rb new file mode 100755 index 0000000..3d10251 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/get_module_path_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'get_module_path function' do + describe 'success' do + it 'get_module_paths dne' do + pp = <<-EOS + $a = $::is_pe ? { + 'true' => '/etc/puppetlabs/puppet/modules/dne', + 'false' => '/etc/puppet/modules/dne', + } + $o = get_module_path('dne') + if $o == $a { + notify { 'output correct': } + } else { + notify { "failed; module path is '$o'": } + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/getparam_spec.rb b/environments/production/modules/stdlib/spec/acceptance/getparam_spec.rb new file mode 100755 index 0000000..bd12154 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/getparam_spec.rb @@ -0,0 +1,24 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'getparam function' do + describe 'success' do + it 'getparam a notify' do + pp = <<-EOS + notify { 'rspec': + message => 'custom rspec message', + } + $o = getparam(Notify['rspec'], 'message') + notice(inline_template('getparam is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/getparam is "custom rspec message"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/getvar_spec.rb b/environments/production/modules/stdlib/spec/acceptance/getvar_spec.rb new file mode 100755 index 0000000..605cdce --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/getvar_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'getvar function' do + describe 'success' do + it 'getvars from classes' do + pp = <<-EOS + class a::data { $foo = 'aoeu' } + include a::data + $b = 'aoeu' + $o = getvar("a::data::foo") + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-numbers' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/grep_spec.rb b/environments/production/modules/stdlib/spec/acceptance/grep_spec.rb new file mode 100755 index 0000000..7c35ee4 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/grep_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'grep function' do + describe 'success' do + it 'greps arrays' do + pp = <<-EOS + $a = ['aaabbb','bbbccc','dddeee'] + $b = 'bbb' + $c = ['aaabbb','bbbccc'] + $o = grep($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/has_interface_with_spec.rb b/environments/production/modules/stdlib/spec/acceptance/has_interface_with_spec.rb new file mode 100755 index 0000000..fd33af5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/has_interface_with_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_interface_with function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_interface_with existing ipaddress' do + pp = <<-EOS + $a = $::ipaddress + $o = has_interface_with('ipaddress', $a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is true/) + end + end + it 'has_interface_with absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.1' + $o = has_interface_with('ipaddress', $a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is false/) + end + end + it 'has_interface_with existing interface' do + pp = <<-EOS + if $osfamily == 'Solaris' or $osfamily == 'Darwin' { + $a = 'lo0' + }elsif $osfamily == 'windows' { + $a = $::kernelmajversion ? { + /6\.(2|3|4)/ => 'Ethernet0', + /6\.(0|1)/ => 'Local_Area_Connection', + /5\.(1|2)/ => undef, #Broken current in facter + } + }else { + $a = 'lo' + } + $o = has_interface_with($a) + notice(inline_template('has_interface_with is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_interface_with is true/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/has_ip_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/has_ip_address_spec.rb new file mode 100755 index 0000000..878d921 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/has_ip_address_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_ip_address function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_ip_address existing ipaddress' do + pp = <<-EOS + $a = '127.0.0.1' + $o = has_ip_address($a) + notice(inline_template('has_ip_address is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_address is true/) + end + end + it 'has_ip_address absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.1' + $o = has_ip_address($a) + notice(inline_template('has_ip_address is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_address is false/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/has_ip_network_spec.rb b/environments/production/modules/stdlib/spec/acceptance/has_ip_network_spec.rb new file mode 100755 index 0000000..f7a7d35 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/has_ip_network_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_ip_network function', :unless => ((fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do + describe 'success' do + it 'has_ip_network existing ipaddress' do + pp = <<-EOS + $a = '127.0.0.0' + $o = has_ip_network($a) + notice(inline_template('has_ip_network is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_network is true/) + end + end + it 'has_ip_network absent ipaddress' do + pp = <<-EOS + $a = '128.0.0.0' + $o = has_ip_network($a) + notice(inline_template('has_ip_network is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/has_ip_network is false/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/has_key_spec.rb b/environments/production/modules/stdlib/spec/acceptance/has_key_spec.rb new file mode 100755 index 0000000..661c122 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/has_key_spec.rb @@ -0,0 +1,41 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'has_key function' do + describe 'success' do + it 'has_keys in hashes' do + pp = <<-EOS + $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' } + $b = 'bbb' + $c = true + $o = has_key($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'has_keys not in hashes' do + pp = <<-EOS + $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' } + $b = 'ccc' + $c = false + $o = has_key($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-hashes' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/hash_spec.rb b/environments/production/modules/stdlib/spec/acceptance/hash_spec.rb new file mode 100755 index 0000000..85da50b --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/hash_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'hash function' do + describe 'success' do + it 'hashs arrays' do + pp = <<-EOS + $a = ['aaa','bbb','bbb','ccc','ddd','eee'] + $b = { 'aaa' => 'bbb', 'bbb' => 'ccc', 'ddd' => 'eee' } + $o = hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles odd-length arrays' + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/intersection_spec.rb b/environments/production/modules/stdlib/spec/acceptance/intersection_spec.rb new file mode 100755 index 0000000..02d4e7d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/intersection_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'intersection function' do + describe 'success' do + it 'intersections arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = ['bbb','ccc','ddd','eee'] + $c = ['bbb','ccc'] + $o = intersection($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'intersections empty arrays' + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_a_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_a_spec.rb new file mode 100644 index 0000000..fb0019a --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_a_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +if get_puppet_version =~ /^4/ + describe 'is_a function' do + it 'should match a string' do + pp = <<-EOS + if 'hello world'.is_a(String) { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + + it 'should not match a integer as string' do + pp = <<-EOS + if 5.is_a(String) { + notify { 'output wrong': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).not_to match(/Notice: output wrong/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_array_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_array_spec.rb new file mode 100755 index 0000000..1a83417 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_array_spec.rb @@ -0,0 +1,67 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_array function' do + describe 'success' do + it 'is_arrays arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = true + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays empty arrays' do + pp = <<-EOS + $a = [] + $b = true + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_arrays hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = false + $o = is_array($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_bool_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_bool_spec.rb new file mode 100755 index 0000000..823cb46 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_bool_spec.rb @@ -0,0 +1,81 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_bool function' do + describe 'success' do + it 'is_bools arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools true' do + pp = <<-EOS + $a = true + $b = true + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools false' do + pp = <<-EOS + $a = false + $b = true + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools strings' do + pp = <<-EOS + $a = "true" + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_bools hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = false + $o = is_bool($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_domain_name_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_domain_name_spec.rb new file mode 100755 index 0000000..884b0bc --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_domain_name_spec.rb @@ -0,0 +1,83 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_domain_name function' do + describe 'success' do + it 'is_domain_names arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names true' do + pp = <<-EOS + $a = true + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names false' do + pp = <<-EOS + $a = false + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + it 'is_domain_names strings with hyphens' do + pp = <<-EOS + $a = "3foo-bar.2bar-fuzz.com" + $b = true + $o = is_domain_name($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_domain_names strings beginning with hyphens' do + pp = <<-EOS + $a = "-bar.2bar-fuzz.com" + $b = false + $o = is_domain_name($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_domain_names hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $o = is_domain_name($a) + notice(inline_template('is_domain_name is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_domain_name is false/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_float_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_float_spec.rb new file mode 100755 index 0000000..0b38d94 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_float_spec.rb @@ -0,0 +1,86 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_float function' do + describe 'success' do + it 'is_floats arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + it 'is_floats true' do + pp = <<-EOS + $a = true + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + it 'is_floats strings' do + pp = <<-EOS + $a = "3.5" + $b = true + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats floats' do + pp = <<-EOS + $a = 3.5 + $b = true + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats integers' do + pp = <<-EOS + $a = 3 + $b = false + $o = is_float($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_floats hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $o = is_float($a) + notice(inline_template('is_float is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_float is false/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_function_available_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_function_available_spec.rb new file mode 100755 index 0000000..f8191ee --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_function_available_spec.rb @@ -0,0 +1,58 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_function_available function' do + describe 'success' do + it 'is_function_availables arrays' do + pp = <<-EOS + $a = ['fail','include','require'] + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is false/) + end + end + it 'is_function_availables true' do + pp = <<-EOS + $a = true + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is false/) + end + end + it 'is_function_availables strings' do + pp = <<-EOS + $a = "fail" + $b = true + $o = is_function_available($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_function_availables function_availables' do + pp = <<-EOS + $a = "is_function_available" + $o = is_function_available($a) + notice(inline_template('is_function_available is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_function_available is true/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_hash_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_hash_spec.rb new file mode 100755 index 0000000..64f016c --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_hash_spec.rb @@ -0,0 +1,63 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_hash function' do + describe 'success' do + it 'is_hashs arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $o = is_hash($a) + notice(inline_template('is_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_hash is false/) + end + end + it 'is_hashs empty hashs' do + pp = <<-EOS + $a = {} + $b = true + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_hashs strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_hashs hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb'} + $b = true + $o = is_hash($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_integer_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_integer_spec.rb new file mode 100755 index 0000000..7333687 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_integer_spec.rb @@ -0,0 +1,95 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_integer function' do + describe 'success' do + it 'is_integers arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers true' do + pp = <<-EOS + $a = true + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers strings' do + pp = <<-EOS + $a = "3" + $b = true + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers floats' do + pp = <<-EOS + $a = 3.5 + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers integers' do + pp = <<-EOS + $a = 3 + $b = true + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_integers hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_integer($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_ip_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_ip_address_spec.rb new file mode 100755 index 0000000..2c62c77 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_ip_address_spec.rb @@ -0,0 +1,80 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ip_address function' do + describe 'success' do + it 'is_ip_addresss ipv4' do + pp = <<-EOS + $a = '1.2.3.4' + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv6' do + pp = <<-EOS + $a = "fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74" + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv6 compressed' do + pp = <<-EOS + $a = "fe00::1" + $b = true + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ip_addresss ipv4 out of range' do + pp = <<-EOS + $a = '1.2.3.400' + $b = false + $o = is_ip_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb new file mode 100755 index 0000000..abe26d8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_ipv4_address_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ipv4_address function' do + describe 'success' do + it 'is_ipv4_addresss' do + pp = <<-EOS + $a = '1.2.3.4' + $b = true + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv4_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv4_addresss ipv4 out of range' do + pp = <<-EOS + $a = '1.2.3.400' + $b = false + $o = is_ipv4_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb new file mode 100755 index 0000000..73a3fa4 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_ipv6_address_spec.rb @@ -0,0 +1,66 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_ipv6_address function' do + describe 'success' do + it 'is_ipv6_addresss' do + pp = <<-EOS + $a = "fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74" + $b = true + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss ipv6 compressed' do + pp = <<-EOS + $a = "fe00::1" + $b = true + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss strings' do + pp = <<-EOS + $a = "aoeu" + $b = false + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_ipv6_addresss ip out of range' do + pp = <<-EOS + $a = 'fe80:0000:cd12:d123:e2f8:47ff:fe09:gggg' + $b = false + $o = is_ipv6_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_mac_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_mac_address_spec.rb new file mode 100755 index 0000000..617bef6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_mac_address_spec.rb @@ -0,0 +1,38 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_mac_address function' do + describe 'success' do + it 'is_mac_addresss a mac' do + pp = <<-EOS + $a = '00:a0:1f:12:7f:a0' + $b = true + $o = is_mac_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_mac_addresss a mac out of range' do + pp = <<-EOS + $a = '00:a0:1f:12:7f:g0' + $b = false + $o = is_mac_address($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_numeric_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_numeric_spec.rb new file mode 100755 index 0000000..7e65384 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_numeric_spec.rb @@ -0,0 +1,95 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_numeric function' do + describe 'success' do + it 'is_numerics arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics true' do + pp = <<-EOS + $a = true + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics strings' do + pp = <<-EOS + $a = "3" + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics floats' do + pp = <<-EOS + $a = 3.5 + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics integers' do + pp = <<-EOS + $a = 3 + $b = true + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_numerics hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_numeric($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + it 'handles non-arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/is_string_spec.rb b/environments/production/modules/stdlib/spec/acceptance/is_string_spec.rb new file mode 100755 index 0000000..bee5e01 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/is_string_spec.rb @@ -0,0 +1,113 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'is_string function' do + describe 'success' do + it 'is_strings arrays' do + pp = <<-EOS + $a = ['aaa.com','bbb','ccc'] + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings true' do + pp = <<-EOS + $a = true + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings strings' do + pp = <<-EOS + $a = "aoeu" + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is true/) + end + end + it 'is_strings number strings' do + pp = <<-EOS + $a = "3" + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is false/) + end + end + it 'is_strings floats' do + pp = <<-EOS + $a = 3.5 + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings integers' do + pp = <<-EOS + $a = 3 + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings hashes' do + pp = <<-EOS + $a = {'aaa'=>'www.com'} + $b = false + $o = is_string($a) + if $o == $b { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'is_strings undef' do + pp = <<-EOS + $a = undef + $o = is_string($a) + notice(inline_template('is_string is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/is_string is true/) + end + end + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb b/environments/production/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb new file mode 100755 index 0000000..ae6947e --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb @@ -0,0 +1,24 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'join_keys_to_values function' do + describe 'success' do + it 'join_keys_to_valuess hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb','ccc'=>'ddd'} + $b = ':' + $o = join_keys_to_values($a,$b) + notice(inline_template('join_keys_to_values is <%= @o.sort.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/join_keys_to_values is \["aaa:bbb", "ccc:ddd"\]/) + end + end + it 'handles non hashes' + it 'handles empty hashes' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/join_spec.rb b/environments/production/modules/stdlib/spec/acceptance/join_spec.rb new file mode 100755 index 0000000..75b88d8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/join_spec.rb @@ -0,0 +1,26 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'join function' do + describe 'success' do + it 'joins arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = ':' + $c = 'aaa:bbb:ccc' + $o = join($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + it 'handles non arrays' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/keys_spec.rb b/environments/production/modules/stdlib/spec/acceptance/keys_spec.rb new file mode 100755 index 0000000..65bfe28 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/keys_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'keys function' do + describe 'success' do + it 'keyss hashes' do + pp = <<-EOS + $a = {'aaa'=>'bbb','ccc'=>'ddd'} + $o = keys($a) + notice(inline_template('keys is <%= @o.sort.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/keys is \["aaa", "ccc"\]/) + end + end + it 'handles non hashes' + it 'handles empty hashes' + end + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/loadjson_spec.rb b/environments/production/modules/stdlib/spec/acceptance/loadjson_spec.rb new file mode 100644 index 0000000..ebd5307 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/loadjson_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +tmpdir = default.tmpdir('stdlib') + +describe 'loadjson function' do + describe 'success' do + it 'loadjsons array of values' do + shell("echo '{\"aaa\":1,\"bbb\":2,\"ccc\":3,\"ddd\":4}' > #{tmpdir}/testjson.json") + pp = <<-EOS + $o = loadjson('#{tmpdir}/testjson.json') + notice(inline_template('loadjson[aaa] is <%= @o["aaa"].inspect %>')) + notice(inline_template('loadjson[bbb] is <%= @o["bbb"].inspect %>')) + notice(inline_template('loadjson[ccc] is <%= @o["ccc"].inspect %>')) + notice(inline_template('loadjson[ddd] is <%= @o["ddd"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[aaa\] is 1/) + expect(r.stdout).to match(/loadjson\[bbb\] is 2/) + expect(r.stdout).to match(/loadjson\[ccc\] is 3/) + expect(r.stdout).to match(/loadjson\[ddd\] is 4/) + end + end + + it 'returns the default value if there is no file to load' do + pp = <<-EOS + $o = loadjson('#{tmpdir}/no-file.json', {'default' => 'value'}) + notice(inline_template('loadjson[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[default\] is "value"/) + end + end + + it 'returns the default value if the file was parsed with an error' do + shell("echo '!' > #{tmpdir}/testjson.json") + pp = <<-EOS + $o = loadjson('#{tmpdir}/testjson.json', {'default' => 'value'}) + notice(inline_template('loadjson[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadjson\[default\] is "value"/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/loadyaml_spec.rb b/environments/production/modules/stdlib/spec/acceptance/loadyaml_spec.rb new file mode 100644 index 0000000..57fb8cb --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/loadyaml_spec.rb @@ -0,0 +1,56 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +tmpdir = default.tmpdir('stdlib') + +describe 'loadyaml function' do + describe 'success' do + it 'loadyamls array of values' do + shell("echo '--- + aaa: 1 + bbb: 2 + ccc: 3 + ddd: 4' > #{tmpdir}/testyaml.yaml") + pp = <<-EOS + $o = loadyaml('#{tmpdir}/testyaml.yaml') + notice(inline_template('loadyaml[aaa] is <%= @o["aaa"].inspect %>')) + notice(inline_template('loadyaml[bbb] is <%= @o["bbb"].inspect %>')) + notice(inline_template('loadyaml[ccc] is <%= @o["ccc"].inspect %>')) + notice(inline_template('loadyaml[ddd] is <%= @o["ddd"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[aaa\] is 1/) + expect(r.stdout).to match(/loadyaml\[bbb\] is 2/) + expect(r.stdout).to match(/loadyaml\[ccc\] is 3/) + expect(r.stdout).to match(/loadyaml\[ddd\] is 4/) + end + end + + it 'returns the default value if there is no file to load' do + pp = <<-EOS + $o = loadyaml('#{tmpdir}/no-file.yaml', {'default' => 'value'}) + notice(inline_template('loadyaml[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[default\] is "value"/) + end + end + + it 'returns the default value if the file was parsed with an error' do + shell("echo '!' > #{tmpdir}/testyaml.yaml") + pp = <<-EOS + $o = loadyaml('#{tmpdir}/testyaml.yaml', {'default' => 'value'}) + notice(inline_template('loadyaml[default] is <%= @o["default"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/loadyaml\[default\] is "value"/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/lstrip_spec.rb b/environments/production/modules/stdlib/spec/acceptance/lstrip_spec.rb new file mode 100755 index 0000000..eba5d0d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/lstrip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'lstrip function' do + describe 'success' do + it 'lstrips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = lstrip($a) + notice(inline_template('lstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/lstrip is \["the ", "public ", "art", "galleries "\]/) + end + end + it 'lstrips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = lstrip($a) + notice(inline_template('lstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/lstrip is "blowzy night-frumps vex'd jack q "/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/max_spec.rb b/environments/production/modules/stdlib/spec/acceptance/max_spec.rb new file mode 100755 index 0000000..3caa813 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/max_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'max function' do + describe 'success' do + it 'maxs arrays' do + pp = <<-EOS + $o = max("the","public","art","galleries") + notice(inline_template('max is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/max is "the"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/member_spec.rb b/environments/production/modules/stdlib/spec/acceptance/member_spec.rb new file mode 100755 index 0000000..2bcadd3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/member_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'member function' do + shared_examples 'item found' do + it 'should output correctly' do + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + end + describe 'success' do + it 'members arrays' do + pp = <<-EOS + $a = ['aaa','bbb','ccc'] + $b = 'ccc' + $c = true + $o = member($a,$b) + if $o == $c { + notify { 'output correct': } + } + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/Notice: output correct/) + end + end + describe 'members array of integers' do + it_should_behave_like 'item found' do + let(:pp) { <<-EOS + if member( [1,2,3,4], 4 ){ + notify { 'output correct': } + } + EOS + } + end + end + describe 'members of mixed array' do + it_should_behave_like 'item found' do + let(:pp) { <<-EOS + if member( ['a','4',3], 'a' ){ + notify { 'output correct': } +} + EOS + } + end + end + it 'members arrays without members' + end + + describe 'failure' do + it 'handles improper argument counts' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/merge_spec.rb b/environments/production/modules/stdlib/spec/acceptance/merge_spec.rb new file mode 100755 index 0000000..814db4e --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/merge_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'merge function' do + describe 'success' do + it 'should merge two hashes' do + pp = <<-EOS + $a = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } + $b = {'two' => 'dos', 'three' => { 'five' => 5 } } + $o = merge($a, $b) + notice(inline_template('merge[one] is <%= @o["one"].inspect %>')) + notice(inline_template('merge[two] is <%= @o["two"].inspect %>')) + notice(inline_template('merge[three] is <%= @o["three"].inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/merge\[one\] is ("1"|1)/) + expect(r.stdout).to match(/merge\[two\] is "dos"/) + expect(r.stdout).to match(/merge\[three\] is {"five"=>("5"|5)}/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/min_spec.rb b/environments/production/modules/stdlib/spec/acceptance/min_spec.rb new file mode 100755 index 0000000..7b18fac --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/min_spec.rb @@ -0,0 +1,20 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'min function' do + describe 'success' do + it 'mins arrays' do + pp = <<-EOS + $o = min("the","public","art","galleries") + notice(inline_template('min is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/min is "art"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml new file mode 100644 index 0000000..5eebdef --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/centos-7-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-7-x64: + roles: + - agent + - default + platform: el-7-x86_64 + hypervisor: vagrant + box: puppetlabs/centos-7.2-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml new file mode 100644 index 0000000..fef6e63 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/debian-8-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + debian-8-x64: + roles: + - agent + - default + platform: debian-8-amd64 + hypervisor: vagrant + box: puppetlabs/debian-8.2-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/default.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..dba339c --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/default.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-1404-x64: + roles: + - agent + - default + platform: ubuntu-14.04-amd64 + hypervisor: vagrant + box: puppetlabs/ubuntu-14.04-64-nocm +CONFIG: + type: foss diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml new file mode 100644 index 0000000..a3333aa --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/centos-7.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-7-x64: + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: '["/usr/sbin/init"]' + # install various tools required to get the image up to usable levels + docker_image_commands: + - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml new file mode 100644 index 0000000..df5c319 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/debian-8.yml @@ -0,0 +1,11 @@ +HOSTS: + debian-8-x64: + platform: debian-8-amd64 + hypervisor: docker + image: debian:8 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml new file mode 100644 index 0000000..b1efa58 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/nodesets/docker/ubuntu-14.04.yml @@ -0,0 +1,12 @@ +HOSTS: + ubuntu-1404-x64: + platform: ubuntu-14.04-amd64 + hypervisor: docker + image: ubuntu:14.04 + docker_preserve_image: true + docker_cmd: '["/sbin/init"]' + docker_image_commands: + # ensure that upstart is booting correctly in the container + - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8' +CONFIG: + trace_limit: 200 diff --git a/environments/production/modules/stdlib/spec/acceptance/num2bool_spec.rb b/environments/production/modules/stdlib/spec/acceptance/num2bool_spec.rb new file mode 100755 index 0000000..00d0ddc --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/num2bool_spec.rb @@ -0,0 +1,76 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'num2bool function' do + describe 'success' do + it 'bools positive numbers and numeric strings as true' do + pp = <<-EOS + $a = 1 + $b = "1" + $c = "50" + $ao = num2bool($a) + $bo = num2bool($b) + $co = num2bool($c) + notice(inline_template('a is <%= @ao.inspect %>')) + notice(inline_template('b is <%= @bo.inspect %>')) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/a is true/) + expect(r.stdout).to match(/b is true/) + expect(r.stdout).to match(/c is true/) + end + end + it 'bools negative numbers as false' do + pp = <<-EOS + $a = 0 + $b = -0.1 + $c = ["-50","1"] + $ao = num2bool($a) + $bo = num2bool($b) + $co = num2bool($c) + notice(inline_template('a is <%= @ao.inspect %>')) + notice(inline_template('b is <%= @bo.inspect %>')) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/a is false/) + expect(r.stdout).to match(/b is false/) + expect(r.stdout).to match(/c is false/) + end + end + end + describe 'failure' do + it 'fails on words' do + pp = <<-EOS + $a = "a" + $ao = num2bool($a) + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/) + end + + it 'fails on numberwords' do + pp = <<-EOS + $b = "1b" + $bo = num2bool($b) + notice(inline_template('b is <%= @bo.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/) + + end + + it 'fails on non-numeric/strings' do + pending "The function will call .to_s.to_i on anything not a Numeric or + String, and results in 0. Is this intended?" + pp = <<-EOS + $c = {"c" => "-50"} + $co = num2bool($c) + notice(inline_template('c is <%= @co.inspect %>')) + EOS + expect(apply_manifest(ppc :expect_failures => true).stderr).to match(/Unable to parse/) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/parsejson_spec.rb b/environments/production/modules/stdlib/spec/acceptance/parsejson_spec.rb new file mode 100755 index 0000000..52133e4 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/parsejson_spec.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'parsejson function' do + describe 'success' do + it 'parses valid json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing"}' + $ao = parsejson($a) + $tests = $ao['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + + describe 'failure' do + it 'raises error on incorrect json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing",}' + $ao = parsejson($a, 'tests are using the default value') + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are using the default value/) + end + end + + it 'raises error on incorrect json' do + pp = <<-EOS + $a = '{"hunter": "washere", "tests": "passing",}' + $ao = parsejson($a) + notice(inline_template('a is <%= @ao.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/expected next name/) + end + end + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = parsejson() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/parseyaml_spec.rb b/environments/production/modules/stdlib/spec/acceptance/parseyaml_spec.rb new file mode 100755 index 0000000..acbda46 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/parseyaml_spec.rb @@ -0,0 +1,58 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'parseyaml function' do + describe 'success' do + it 'parses valid yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n" + $o = parseyaml($a) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + + describe 'failure' do + it 'returns the default value on incorrect yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n:" + $o = parseyaml($a, {'tests' => 'using the default value'}) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "using the default value"/) + end + end + + it 'raises error on incorrect yaml' do + pp = <<-EOS + $a = "---\nhunter: washere\ntests: passing\n:" + $o = parseyaml($a) + $tests = $o['tests'] + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/(syntax error|did not find expected key)/) + end + end + + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = parseyaml() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/pick_default_spec.rb b/environments/production/modules/stdlib/spec/acceptance/pick_default_spec.rb new file mode 100755 index 0000000..e7e25ab --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/pick_default_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'pick_default function' do + describe 'success' do + it 'pick_defaults a default value' do + pp = <<-EOS + $a = undef + $o = pick_default($a, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "default"/) + end + end + it 'pick_defaults with no value' do + pp = <<-EOS + $a = undef + $b = undef + $o = pick_default($a,$b) + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is ""/) + end + end + it 'pick_defaults the first set value' do + pp = <<-EOS + $a = "something" + $b = "long" + $o = pick_default($a, $b, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "something"/) + end + end + end + describe 'failure' do + it 'raises error with no values' do + pp = <<-EOS + $o = pick_default() + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Must receive at least one argument/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/pick_spec.rb b/environments/production/modules/stdlib/spec/acceptance/pick_spec.rb new file mode 100755 index 0000000..c70b2d9 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/pick_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'pick function' do + describe 'success' do + it 'picks a default value' do + pp = <<-EOS + $a = undef + $o = pick($a, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "default"/) + end + end + it 'picks the first set value' do + pp = <<-EOS + $a = "something" + $b = "long" + $o = pick($a, $b, 'default') + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/picked is "something"/) + end + end + end + describe 'failure' do + it 'raises error with all undef values' do + pp = <<-EOS + $a = undef + $b = undef + $o = pick($a, $b) + notice(inline_template('picked is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/must receive at least one non empty value/) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/prefix_spec.rb b/environments/production/modules/stdlib/spec/acceptance/prefix_spec.rb new file mode 100755 index 0000000..58c691d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/prefix_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'prefix function' do + describe 'success' do + it 'prefixes array of values' do + pp = <<-EOS + $o = prefix(['a','b','c'],'p') + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \["pa", "pb", "pc"\]/) + end + end + it 'prefixs with empty array' do + pp = <<-EOS + $o = prefix([],'p') + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \[\]/) + end + end + it 'prefixs array of values with undef' do + pp = <<-EOS + $o = prefix(['a','b','c'], undef) + notice(inline_template('prefix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/prefix is \["a", "b", "c"\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/pw_hash_spec.rb b/environments/production/modules/stdlib/spec/acceptance/pw_hash_spec.rb new file mode 100644 index 0000000..829d087 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/pw_hash_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +# Windows and OS X do not have useful implementations of crypt(3) +describe 'pw_hash function', :unless => (['windows', 'Darwin', 'SLES']).include?(fact('operatingsystem')) do + describe 'success' do + it 'hashes passwords' do + pp = <<-EOS + $o = pw_hash('password', 'sha-512', 'salt') + notice(inline_template('pw_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/pw_hash is "\$6\$salt\$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy\.g\."/) + end + end + + it 'returns nil if no password is provided' do + pp = <<-EOS + $o = pw_hash('', 'sha-512', 'salt') + notice(inline_template('pw_hash is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/pw_hash is nil/) + end + end + end + describe 'failure' do + it 'handles less than three arguments' + it 'handles more than three arguments' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/range_spec.rb b/environments/production/modules/stdlib/spec/acceptance/range_spec.rb new file mode 100755 index 0000000..f57f884 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/range_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'range function' do + describe 'success' do + it 'ranges letters' do + pp = <<-EOS + $o = range('a','d') + notice(inline_template('range is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/range is \["a", "b", "c", "d"\]/) + end + end + it 'ranges letters with a step' do + pp = <<-EOS + $o = range('a','d', '2') + notice(inline_template('range is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/range is \["a", "c"\]/) + end + end + it 'ranges letters with a negative step' + it 'ranges numbers' + it 'ranges numbers with a step' + it 'ranges numbers with a negative step' + it 'ranges numeric strings' + it 'ranges zero padded numbers' + end + describe 'failure' do + it 'fails with no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/reject_spec.rb b/environments/production/modules/stdlib/spec/acceptance/reject_spec.rb new file mode 100755 index 0000000..ce4342d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/reject_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'reject function' do + describe 'success' do + it 'rejects array of values' do + pp = <<-EOS + $o = reject(['aaa','bbb','ccc','aaaddd'], 'aaa') + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \["bbb", "ccc"\]/) + end + end + it 'rejects with empty array' do + pp = <<-EOS + $o = reject([],'aaa') + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \[\]/) + end + end + it 'rejects array of values with undef' do + pp = <<-EOS + $o = reject(['aaa','bbb','ccc','aaaddd'], undef) + notice(inline_template('reject is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reject is \[\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/reverse_spec.rb b/environments/production/modules/stdlib/spec/acceptance/reverse_spec.rb new file mode 100755 index 0000000..3b5dfad --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/reverse_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'reverse function' do + describe 'success' do + it 'reverses strings' do + pp = <<-EOS + $a = "the public art galleries" + # Anagram: Large picture halls, I bet + $o = reverse($a) + notice(inline_template('reverse is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/reverse is "seirellag tra cilbup eht"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/rstrip_spec.rb b/environments/production/modules/stdlib/spec/acceptance/rstrip_spec.rb new file mode 100755 index 0000000..150dac1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/rstrip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'rstrip function' do + describe 'success' do + it 'rstrips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = rstrip($a) + notice(inline_template('rstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/rstrip is \[" the", " public", " art", "galleries"\]/) + end + end + it 'rstrips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = rstrip($a) + notice(inline_template('rstrip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/rstrip is " blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/shuffle_spec.rb b/environments/production/modules/stdlib/spec/acceptance/shuffle_spec.rb new file mode 100755 index 0000000..0738383 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/shuffle_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'shuffle function' do + describe 'success' do + it 'shuffles arrays' do + pp = <<-EOS + $a = ["1", "2", "3", "4", "5", "6", "7", "8", "the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = shuffle($a) + notice(inline_template('shuffle is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to_not match(/shuffle is \["1", "2", "3", "4", "5", "6", "7", "8", "the", "public", "art", "galleries"\]/) + end + end + it 'shuffles strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = shuffle($a) + notice(inline_template('shuffle is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to_not match(/shuffle is "blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/size_spec.rb b/environments/production/modules/stdlib/spec/acceptance/size_spec.rb new file mode 100755 index 0000000..6390c20 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/size_spec.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'size function' do + describe 'success' do + it 'single string size' do + pp = <<-EOS + $a = 'discombobulate' + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 14/) + end + end + it 'with empty string' do + pp = <<-EOS + $a = '' + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 0/) + end + end + it 'with undef' do + pp = <<-EOS + $a = undef + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 0/) + end + end + it 'strings in array' do + pp = <<-EOS + $a = ['discombobulate', 'moo'] + $o = size($a) + notice(inline_template('size is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/size is 2/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/sort_spec.rb b/environments/production/modules/stdlib/spec/acceptance/sort_spec.rb new file mode 100755 index 0000000..e7ff7f7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/sort_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'sort function' do + describe 'success' do + it 'sorts arrays' do + pp = <<-EOS + $a = ["the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = sort($a) + notice(inline_template('sort is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/sort is \["art", "galleries", "public", "the"\]/) + end + end + it 'sorts strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = sort($a) + notice(inline_template('sort is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/sort is " '-abcdefghijklmnopqrstuvwxyz"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/squeeze_spec.rb b/environments/production/modules/stdlib/spec/acceptance/squeeze_spec.rb new file mode 100755 index 0000000..3324691 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/squeeze_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'squeeze function' do + describe 'success' do + it 'squeezes arrays' do + pp = <<-EOS + # Real words! + $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"] + $o = squeeze($a) + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is \["wales", "laparohysterosalpingophorectomy", "br", "godeship"\]/) + end + end + it 'squeezez arrays with an argument' + it 'squeezes strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = squeeze($a) + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is "wales laparohysterosalpingophorectomy br godeship"/) + end + end + + it 'squeezes strings with an argument' do + pp = <<-EOS + $a = "countessship duchessship governessship hostessship" + $o = squeeze($a, 's') + notice(inline_template('squeeze is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/squeeze is "counteship ducheship governeship hosteship"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/str2bool_spec.rb b/environments/production/modules/stdlib/spec/acceptance/str2bool_spec.rb new file mode 100755 index 0000000..9a8c06c --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/str2bool_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'str2bool function' do + describe 'success' do + it 'works with "y"' do + pp = <<-EOS + $o = str2bool('y') + notice(inline_template('str2bool is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/str2bool is true/) + end + end + it 'works with "Y"' + it 'works with "yes"' + it 'works with "1"' + it 'works with "true"' + it 'works with "n"' + it 'works with "N"' + it 'works with "no"' + it 'works with "0"' + it 'works with "false"' + it 'works with undef' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays or strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb b/environments/production/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb new file mode 100755 index 0000000..5f03924 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'str2saltedsha512 function' do + describe 'success' do + it 'works with "y"' do + pp = <<-EOS + $o = str2saltedsha512('password') + notice(inline_template('str2saltedsha512 is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/str2saltedsha512 is "[a-f0-9]{136}"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles more than one argument' + it 'handles non strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/strftime_spec.rb b/environments/production/modules/stdlib/spec/acceptance/strftime_spec.rb new file mode 100755 index 0000000..38521b0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/strftime_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'strftime function' do + describe 'success' do + it 'gives the Century' do + pp = <<-EOS + $o = strftime('%C') + notice(inline_template('strftime is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strftime is "20"/) + end + end + it 'takes a timezone argument' + end + describe 'failure' do + it 'handles no arguments' + it 'handles invalid format strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/strip_spec.rb b/environments/production/modules/stdlib/spec/acceptance/strip_spec.rb new file mode 100755 index 0000000..05cd395 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/strip_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'strip function' do + describe 'success' do + it 'strips arrays' do + pp = <<-EOS + $a = [" the "," public "," art","galleries "] + # Anagram: Large picture halls, I bet + $o = strip($a) + notice(inline_template('strip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strip is \["the", "public", "art", "galleries"\]/) + end + end + it 'strips strings' do + pp = <<-EOS + $a = " blowzy night-frumps vex'd jack q " + $o = strip($a) + notice(inline_template('strip is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/strip is "blowzy night-frumps vex'd jack q"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/suffix_spec.rb b/environments/production/modules/stdlib/spec/acceptance/suffix_spec.rb new file mode 100755 index 0000000..60a6264 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/suffix_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'suffix function' do + describe 'success' do + it 'suffixes array of values' do + pp = <<-EOS + $o = suffix(['a','b','c'],'p') + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \["ap", "bp", "cp"\]/) + end + end + it 'suffixs with empty array' do + pp = <<-EOS + $o = suffix([],'p') + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \[\]/) + end + end + it 'suffixs array of values with undef' do + pp = <<-EOS + $o = suffix(['a','b','c'], undef) + notice(inline_template('suffix is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/suffix is \["a", "b", "c"\]/) + end + end + end + describe 'failure' do + it 'fails with no arguments' + it 'fails when first argument is not array' + it 'fails when second argument is not string' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/swapcase_spec.rb b/environments/production/modules/stdlib/spec/acceptance/swapcase_spec.rb new file mode 100755 index 0000000..9f94c0d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/swapcase_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'swapcase function' do + describe 'success' do + it 'works with strings' do + pp = <<-EOS + $o = swapcase('aBcD') + notice(inline_template('swapcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/swapcase is "AbCd"/) + end + end + it 'works with arrays' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays or strings' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/time_spec.rb b/environments/production/modules/stdlib/spec/acceptance/time_spec.rb new file mode 100755 index 0000000..dae1166 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/time_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'time function' do + describe 'success' do + it 'gives the time' do + pp = <<-EOS + $o = time() + notice(inline_template('time is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/time is (\d+)\D/) + + # When I wrote this test + expect(Integer(m[1])).to be > 1398894170 + end + end + it 'takes a timezone argument' do + pp = <<-EOS + $o = time('UTC') + notice(inline_template('time is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/time is (\d+)\D/) + + expect(Integer(m[1])).to be > 1398894170 + end + end + end + describe 'failure' do + it 'handles more arguments' + it 'handles invalid timezones' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/to_bytes_spec.rb b/environments/production/modules/stdlib/spec/acceptance/to_bytes_spec.rb new file mode 100755 index 0000000..b1015a3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/to_bytes_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'to_bytes function' do + describe 'success' do + it 'converts kB to B' do + pp = <<-EOS + $o = to_bytes('4 kB') + notice(inline_template('to_bytes is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + m = r.stdout.match(/to_bytes is (\d+)\D/) + expect(m[1]).to eq("4096") + end + end + it 'works without the B in unit' + it 'works without a space before unit' + it 'works without a unit' + it 'converts fractions' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non integer arguments' + it 'handles unknown units like uB' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/try_get_value_spec.rb b/environments/production/modules/stdlib/spec/acceptance/try_get_value_spec.rb new file mode 100755 index 0000000..716241c --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/try_get_value_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'try_get_value function' do + describe 'success' do + it 'gets a value' do + pp = <<-EOS + $data = { + 'a' => { 'b' => 'passing'} + } + + $tests = try_get_value($data, 'a/b') + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/tests are "passing"/) + end + end + end + describe 'failure' do + it 'uses a default value' do + pp = <<-EOS + $data = { + 'a' => { 'b' => 'passing'} + } + + $tests = try_get_value($data, 'c/d', 'using the default value') + notice(inline_template('tests are <%= @tests.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/using the default value/) + end + end + + it 'raises error on incorrect number of arguments' do + pp = <<-EOS + $o = try_get_value() + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/wrong number of arguments/i) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/type_spec.rb b/environments/production/modules/stdlib/spec/acceptance/type_spec.rb new file mode 100755 index 0000000..5cc9470 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/type_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'type function' do + describe 'success' do + it 'types arrays' do + pp = <<-EOS + $a = ["the","public","art","galleries"] + # Anagram: Large picture halls, I bet + $o = type($a) + notice(inline_template('type is <%= @o.to_s %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/type is Tuple\[String.*, String.*, String.*, String.*\]/) + end + end + it 'types strings' do + pp = <<-EOS + $a = "blowzy night-frumps vex'd jack q" + $o = type($a) + notice(inline_template('type is <%= @o.to_s %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/type is String/) + end + end + it 'types hashes' + it 'types integers' + it 'types floats' + it 'types booleans' + end + describe 'failure' do + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/union_spec.rb b/environments/production/modules/stdlib/spec/acceptance/union_spec.rb new file mode 100755 index 0000000..7229bf5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/union_spec.rb @@ -0,0 +1,25 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'union function' do + describe 'success' do + it 'unions arrays' do + pp = <<-EOS + $a = ["the","public"] + $b = ["art"] + $c = ["galleries"] + # Anagram: Large picture halls, I bet + $o = union($a,$b,$c) + notice(inline_template('union is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/union is \["the", "public", "art", "galleries"\]/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/unique_spec.rb b/environments/production/modules/stdlib/spec/acceptance/unique_spec.rb new file mode 100755 index 0000000..7fb5eca --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/unique_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'unique function' do + describe 'success' do + it 'uniques arrays' do + pp = <<-EOS + $a = ["wallless", "wallless", "brrr", "goddessship"] + $o = unique($a) + notice(inline_template('unique is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/unique is \["wallless", "brrr", "goddessship"\]/) + end + end + it 'uniques strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = unique($a) + notice(inline_template('unique is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/unique is "wales prohytingcmbd"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/upcase_spec.rb b/environments/production/modules/stdlib/spec/acceptance/upcase_spec.rb new file mode 100755 index 0000000..1782309 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/upcase_spec.rb @@ -0,0 +1,33 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'upcase function' do + describe 'success' do + it 'upcases arrays' do + pp = <<-EOS + $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"] + $o = upcase($a) + notice(inline_template('upcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/upcase is \["WALLLESS", "LAPAROHYSTEROSALPINGOOOPHORECTOMY", "BRRR", "GODDESSSHIP"\]/) + end + end + it 'upcases strings' do + pp = <<-EOS + $a = "wallless laparohysterosalpingooophorectomy brrr goddessship" + $o = upcase($a) + notice(inline_template('upcase is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/upcase is "WALLLESS LAPAROHYSTEROSALPINGOOOPHORECTOMY BRRR GODDESSSHIP"/) + end + end + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/uriescape_spec.rb b/environments/production/modules/stdlib/spec/acceptance/uriescape_spec.rb new file mode 100755 index 0000000..e123425 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/uriescape_spec.rb @@ -0,0 +1,23 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'uriescape function' do + describe 'success' do + it 'uriescape strings' do + pp = <<-EOS + $a = ":/?#[]@!$&'()*+,;= \\\"{}" + $o = uriescape($a) + notice(inline_template('uriescape is <%= @o.inspect %>')) + EOS + + apply_manifest(pp, :catch_failures => true) do |r| + expect(r.stdout).to match(/uriescape is ":\/\?%23\[\]@!\$&'\(\)\*\+,;=%20%22%7B%7D"/) + end + end + it 'does nothing if a string is already safe' + end + describe 'failure' do + it 'handles no arguments' + it 'handles non strings or arrays' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb new file mode 100755 index 0000000..880850d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_absolute_path function' do + describe 'success' do + %w{ + C:/ + C:\\\\ + C:\\\\WINDOWS\\\\System32 + C:/windows/system32 + X:/foo/bar + X:\\\\foo\\\\bar + /var/tmp + /var/lib/puppet + /var/opt/../lib/puppet + }.each do |path| + it "validates a single argument #{path}" do + pp = <<-EOS + $one = '#{path}' + validate_absolute_path($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles relative paths' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_array_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_array_spec.rb new file mode 100755 index 0000000..a76321d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_array_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_array function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = ['a', 'b'] + validate_array($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = ['a', 'b'] + $two = [['c'], 'd'] + validate_array($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_array({'a' => 'hash' })}, + %{validate_array('string')}, + %{validate_array(false)}, + %{validate_array(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/is not an Array\. It looks to be a/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_augeas_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_augeas_spec.rb new file mode 100755 index 0000000..be213d3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_augeas_spec.rb @@ -0,0 +1,63 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_augeas function', :unless => (fact('osfamily') == 'windows') do + describe 'prep' do + it 'installs augeas for tests' + end + describe 'success' do + context 'valid inputs with no 3rd argument' do + { + 'root:x:0:0:root:/root:/bin/bash\n' => 'Passwd.lns', + 'proc /proc proc nodev,noexec,nosuid 0 0\n' => 'Fstab.lns' + }.each do |line,lens| + it "validates a single argument for #{lens}" do + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + validate_augeas($line, $lens) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + end + context 'valid inputs with 3rd and 4th arguments' do + it "validates a restricted value" do + line = 'root:x:0:0:root:/root:/bin/barsh\n' + lens = 'Passwd.lns' + restriction = '$file/*[shell="/bin/barsh"]' + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + $restriction = ['#{restriction}'] + validate_augeas($line, $lens, $restriction, "my custom failure message") + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/my custom failure message/) + end + end + context 'invalid inputs' do + { + 'root:x:0:0:root' => 'Passwd.lns', + '127.0.1.1' => 'Hosts.lns' + }.each do |line,lens| + it "validates a single argument for #{lens}" do + pp = <<-EOS + $line = "#{line}" + $lens = "#{lens}" + validate_augeas($line, $lens) + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + end + context 'garbage inputs' do + it 'raises an error on invalid inputs' + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_bool_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_bool_spec.rb new file mode 100755 index 0000000..993f9ef --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_bool_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_bool function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = true + validate_bool($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = true + $two = false + validate_bool($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_bool('true')}, + %{validate_bool('false')}, + %{validate_bool([true])}, + %{validate_bool(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/is not a boolean\. It looks to be a/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_cmd_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_cmd_spec.rb new file mode 100755 index 0000000..5fc7b94 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_cmd_spec.rb @@ -0,0 +1,52 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_cmd function' do + describe 'success' do + it 'validates a true command' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'echo' #shell built-in + } else { + $two = '/bin/echo' + } + validate_cmd($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a fail command' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'C:/aoeu' + } else { + $two = '/bin/aoeu' + } + validate_cmd($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates a fail command with a custom error message' do + pp = <<-EOS + $one = 'foo' + if $::osfamily == 'windows' { + $two = 'C:/aoeu' + } else { + $two = '/bin/aoeu' + } + validate_cmd($one,$two,"aoeu is dvorak") + EOS + + apply_manifest(pp, :expect_failures => true) do |output| + expect(output.stderr).to match(/aoeu is dvorak/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper argument types' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_hash_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_hash_spec.rb new file mode 100755 index 0000000..fc0f079 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_hash_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_hash function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = { 'a' => 1 } + validate_hash($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = { 'a' => 1 } + $two = { 'b' => 2 } + validate_hash($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + [ + %{validate_hash('{ "not" => "hash" }')}, + %{validate_hash('string')}, + %{validate_hash(["array"])}, + %{validate_hash(undef)} + ].each do |pp| + it "rejects #{pp.inspect}" do + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(//) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb new file mode 100755 index 0000000..67d3139 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_ipv4_address function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = '1.2.3.4' + validate_ipv4_address($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = '1.2.3.4' + $two = '5.6.7.8' + validate_ipv4_address($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles ipv6 addresses' + it 'handles non-ipv4 strings' + it 'handles numbers' + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb new file mode 100755 index 0000000..eaa845d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_ipv6_address function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = '3ffe:0505:0002::' + validate_ipv6_address($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = '3ffe:0505:0002::' + $two = '3ffe:0505:0001::' + validate_ipv6_address($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles ipv6 addresses' + it 'handles non-ipv6 strings' + it 'handles numbers' + it 'handles no arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_re_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_re_spec.rb new file mode 100755 index 0000000..eefb286 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_re_spec.rb @@ -0,0 +1,47 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_re function' do + describe 'success' do + it 'validates a string' do + pp = <<-EOS + $one = 'one' + $two = '^one$' + validate_re($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an array' do + pp = <<-EOS + $one = 'one' + $two = ['^one$', '^two'] + validate_re($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a failed array' do + pp = <<-EOS + $one = 'one' + $two = ['^two$', '^three'] + validate_re($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates a failed array with a custom error message' do + pp = <<-EOS + $one = '3.4.3' + $two = '^2.7' + validate_re($one,$two,"The $puppetversion fact does not match 2.7") + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/does not match/) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper argument types' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_slength_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_slength_spec.rb new file mode 100755 index 0000000..c29fd23 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_slength_spec.rb @@ -0,0 +1,72 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_slength function' do + describe 'success' do + it 'validates a single string max' do + pp = <<-EOS + $one = 'discombobulate' + $two = 17 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates multiple string maxes' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates min/max of strings in array' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + $three = 3 + validate_slength($one,$two,$three) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a single string max of incorrect length' do + pp = <<-EOS + $one = 'discombobulate' + $two = 1 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates multiple string maxes of incorrect length' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 3 + validate_slength($one,$two) + EOS + + apply_manifest(pp, :expect_failures => true) + end + it 'validates multiple strings min/maxes of incorrect length' do + pp = <<-EOS + $one = ['discombobulate', 'moo'] + $two = 17 + $three = 10 + validate_slength($one,$two,$three) + EOS + + apply_manifest(pp, :expect_failures => true) + end + end + describe 'failure' do + it 'handles improper number of arguments' + it 'handles improper first argument type' + it 'handles non-strings in array of first argument' + it 'handles improper second argument type' + it 'handles improper third argument type' + it 'handles negative ranges' + it 'handles improper ranges' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/validate_string_spec.rb b/environments/production/modules/stdlib/spec/acceptance/validate_string_spec.rb new file mode 100755 index 0000000..f04608d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/validate_string_spec.rb @@ -0,0 +1,43 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'validate_string function' do + describe 'success' do + it 'validates a single argument' do + pp = <<-EOS + $one = 'string' + validate_string($one) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates an multiple arguments' do + pp = <<-EOS + $one = 'string' + $two = 'also string' + validate_string($one,$two) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates undef' do + pp = <<-EOS + validate_string(undef) + EOS + + apply_manifest(pp, :catch_failures => true) + end + it 'validates a non-string' do + { + %{validate_string({ 'a' => 'hash' })} => "Hash", + %{validate_string(['array'])} => "Array", + %{validate_string(false)} => "FalseClass", + }.each do |pp,type| + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/) + end + end + end + describe 'failure' do + it 'handles improper number of arguments' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/values_at_spec.rb b/environments/production/modules/stdlib/spec/acceptance/values_at_spec.rb new file mode 100755 index 0000000..eb0bf4f --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/values_at_spec.rb @@ -0,0 +1,73 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'values_at function' do + describe 'success' do + it 'returns a specific value' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = 1 + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b"\]/) + end + it 'returns a specific negative index value' do + pending("negative numbers don't work") + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = -1 + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["e"\]/) + end + it 'returns a range of values' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = "1-3" + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d"\]/) + end + it 'returns a negative specific value and range of values' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = ["1-3",0] + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d", "a"\]/) + end + end + describe 'failure' do + it 'handles improper number of arguments' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $output = values_at($one) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/) + end + it 'handles non-indicies arguments' do + pp = <<-EOS + $one = ['a','b','c','d','e'] + $two = [] + $output = values_at($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/at least one positive index/) + end + + it 'detects index ranges smaller than the start range' + it 'handles index ranges larger than array' + it 'handles non-integer indicies' + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/values_spec.rb b/environments/production/modules/stdlib/spec/acceptance/values_spec.rb new file mode 100755 index 0000000..cef1c9d --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/values_spec.rb @@ -0,0 +1,30 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'values function' do + describe 'success' do + it 'returns an array of values' do + pp = <<-EOS + $arg = { + 'a' => 1, + 'b' => 2, + 'c' => 3, + } + $output = values($arg) + notice(inline_template('<%= @output.sort.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 2, 3\]/) + end + end + describe 'failure' do + it 'handles non-hash arguments' do + pp = <<-EOS + $arg = "foo" + $output = values($arg) + notice(inline_template('<%= @output.inspect %>')) + EOS + + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires hash/) + end + end +end diff --git a/environments/production/modules/stdlib/spec/acceptance/zip_spec.rb b/environments/production/modules/stdlib/spec/acceptance/zip_spec.rb new file mode 100755 index 0000000..ae22896 --- /dev/null +++ b/environments/production/modules/stdlib/spec/acceptance/zip_spec.rb @@ -0,0 +1,67 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper_acceptance' + +describe 'zip function' do + describe 'success' do + it 'zips two arrays of numbers together' do + pp = <<-EOS + $one = [1,2,3,4] + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\], \[3, 7\], \[4, 8\]\]/) + end + it 'zips two arrays of numbers & bools together' do + pp = <<-EOS + $one = [1,2,"three",4] + $two = [true,true,false,false] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, true\], \[2, true\], \["three", false\], \[4, false\]\]/) + end + it 'zips two arrays of numbers together and flattens them' do + # XXX This only tests the argument `true`, even though the following are valid: + # 1 t y true yes + # 0 f n false no + # undef undefined + pp = <<-EOS + $one = [1,2,3,4] + $two = [5,6,7,8] + $output = zip($one,$two,true) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 5, 2, 6, 3, 7, 4, 8\]/) + end + it 'handles unmatched length' do + # XXX Is this expected behavior? + pp = <<-EOS + $one = [1,2] + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\]\]/) + end + end + describe 'failure' do + it 'handles improper number of arguments' do + pp = <<-EOS + $one = [1,2] + $output = zip($one) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/) + end + it 'handles improper argument types' do + pp = <<-EOS + $one = "a string" + $two = [5,6,7,8] + $output = zip($one,$two) + notice(inline_template('<%= @output.inspect %>')) + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires array/) + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/absolute_path_spec.rb b/environments/production/modules/stdlib/spec/aliases/absolute_path_spec.rb new file mode 100644 index 0000000..308a3aa --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/absolute_path_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::absolute_path', type: :class do + describe 'valid paths handling' do + %w{ + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + / + /var/tmp + /var/opt/../lib/puppet + /var/opt//lib/puppet + /var/ůťƒ8 + /var/ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Absolute_path/) } + end + end + end + + context 'relative paths' do + %w{ + relative1 + . + .. + ./foo + ../foo + etc/puppetlabs/puppet + opt/puppet/bin + relative\\windows + \var\ůťƒ8 + \var\ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Absolute_path/) } + end + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/absolutepath_spec.rb b/environments/production/modules/stdlib/spec/aliases/absolutepath_spec.rb new file mode 100644 index 0000000..cd442f2 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/absolutepath_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::absolutepath', type: :class do + describe 'valid handling' do + %w{ + /usr2/username/bin:/usr/local/bin:/usr/bin:. + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + /var/tmp + /var/opt/../lib/puppet + /var/opt//lib/puppet + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "*/Users//nope", + "\\Users/hc/wksp/stdlib", + "C:noslashes", + "\\var\\tmp" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Variant/) } + end + end + end + + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/array_spec.rb b/environments/production/modules/stdlib/spec/aliases/array_spec.rb new file mode 100644 index 0000000..89cc2af --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/array_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::array', type: :class do + describe 'accepts arrays' do + [ + [], + ['one'], + [1], + [{}], + [[]], + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + '', + 'one', + '1', + {}, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Array/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/bool_spec.rb b/environments/production/modules/stdlib/spec/aliases/bool_spec.rb new file mode 100644 index 0000000..b84ab51 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/bool_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::bool', type: :class do + describe 'accepts booleans' do + [ + true, + false, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + [1], + [{}], + [true], + 'true', + 'false', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Bool/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/float_spec.rb b/environments/production/modules/stdlib/spec/aliases/float_spec.rb new file mode 100644 index 0000000..66079c6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/float_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::float', type: :class do + describe 'accepts floats' do + [ + 3.7, + '3.7', + -3.7, + '-342.2315e-12', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x', 3, '3', -3, '-3'].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Float or Pattern(\[.*\]+)?/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/hash_spec.rb b/environments/production/modules/stdlib/spec/aliases/hash_spec.rb new file mode 100644 index 0000000..6e5060d --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/hash_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::hash', type: :class do + describe 'accepts hashes' do + [ + {}, + {'one' => "two"}, + {'wan' => 3}, + {'001' => "helly"}, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + '', + 'one', + '1', + [], + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a Stdlib::Compat::Hash/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/httpsurl_spec.rb b/environments/production/modules/stdlib/spec/aliases/httpsurl_spec.rb new file mode 100644 index 0000000..3e51118 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/httpsurl_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::httpsurl', type: :class do + describe 'valid handling' do + %w{ + https://hello.com + https://notcreative.org + https://notexciting.co.uk + https://graphemica.com/❤ + https://graphemica.com/緩 + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "hptts:/nah", + "https;//notrightbutclose.org", + "http://graphemica.com/❤", + "http://graphemica.com/緩" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::HTTPSUrl/) } + end + end + end + + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/httpurl_spec.rb b/environments/production/modules/stdlib/spec/aliases/httpurl_spec.rb new file mode 100644 index 0000000..fd49a47 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/httpurl_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::httpurl', type: :class do + describe 'valid handling' do + %w{ + https://hello.com + https://notcreative.org + https://canstillaccepthttps.co.uk + http://anhttp.com + http://runningoutofideas.gov + http:// + http://graphemica.com/❤ + http://graphemica.com/緩 + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "hptts:/nah", + "https;//notrightbutclose.org", + "hts://graphemica.com/❤", + "https:graphemica.com/緩" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::HTTPUrl/) } + end + end + end + + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/integer_spec.rb b/environments/production/modules/stdlib/spec/aliases/integer_spec.rb new file mode 100644 index 0000000..bd00c2a --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/integer_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::integer', type: :class do + describe 'accepts integers' do + [ + 3, + '3', + -3, + '-3', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x', 3.7, '3.7',-3.7, '-342.2315e-12' ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Integer, Pattern(\[.*\]+)?, or Array/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/ip_address.rb b/environments/production/modules/stdlib/spec/aliases/ip_address.rb new file mode 100644 index 0000000..67a555c --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/ip_address.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ip_address', type: :class do + describe 'accepts ipv4 and ipv6 addresses' do + [ + '224.0.0.0', + '255.255.255.255', + '0.0.0.0', + '192.88.99.0', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fa76:8765:34ac:0823:ab76:eee9:0987:1111' + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + 'nope', + '77', + '4.4.4', + '2001:0db8:85a3:000000:0000:8a2e:0370:7334' + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/ipv4_spec.rb b/environments/production/modules/stdlib/spec/aliases/ipv4_spec.rb new file mode 100644 index 0000000..6a503ad --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/ipv4_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ipv4', type: :class do + describe 'accepts ipv4 addresses' do + SharedData::IPV4_PATTERNS.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Ipv4/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/ipv6_spec.rb b/environments/production/modules/stdlib/spec/aliases/ipv6_spec.rb new file mode 100644 index 0000000..ae90f42 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/ipv6_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::ipv6', type: :class do + describe 'accepts ipv6 addresses' do + [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fa76:8765:34ac:0823:ab76:eee9:0987:1111', + 'fe80:0000:0000:0000:0204:61ff:fe9d:f156', + 'fe80:0:0:0:204:61ff:fe9d:f156', + 'fe80::204:61ff:fe9d:f156', + 'fe80:0:0:0:0204:61ff:254.157.241.86', + '::1', + 'fe80::', + '2001::', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + describe 'rejects other values' do + [ + 'nope', + '77', + '4.4.4', + '2000:7334', + '::ffff:2.3.4', + '::ffff:257.1.2.3', + '::ffff:12345678901234567890.1.26', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Compat::Ipv6/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/numeric_spec.rb b/environments/production/modules/stdlib/spec/aliases/numeric_spec.rb new file mode 100644 index 0000000..bc17f4f --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/numeric_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::numeric', type: :class do + describe 'accepts numerics' do + [ + 3, + '3', + -3, + '-3', + 3.7, + '3.7', + -3.7, + '-342.2315e-12', + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x' ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a value of type Numeric, Pattern(\[.*\]+)?, or Array/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/string_spec.rb b/environments/production/modules/stdlib/spec/aliases/string_spec.rb new file mode 100644 index 0000000..d8fb885 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/string_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::string', type: :class do + describe 'accepts strings' do + [ + '', + 'one', + nil, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'rejects other values' do + [ + [], + {}, + 1, + true, + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a String/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/unixpath_spec.rb b/environments/production/modules/stdlib/spec/aliases/unixpath_spec.rb new file mode 100644 index 0000000..6aecfe7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/unixpath_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::unixpath', type: :class do + describe 'valid handling' do + %w{ + /usr2/username/bin:/usr/local/bin:/usr/bin:. + /var/tmp + /Users/helencampbell/workspace/puppetlabs-stdlib + /var/ůťƒ8 + /var/ネット + /var//tmp + /var/../tmp + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "C:/whatever", + "\\var\\tmp", + "\\Users/hc/wksp/stdlib", + "*/Users//nope", + "var\ůťƒ8", + "var\ネット" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Unixpath/) } + end + end + end + + end + end +end diff --git a/environments/production/modules/stdlib/spec/aliases/windowspath_spec.rb b/environments/production/modules/stdlib/spec/aliases/windowspath_spec.rb new file mode 100644 index 0000000..c20e373 --- /dev/null +++ b/environments/production/modules/stdlib/spec/aliases/windowspath_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'test::windowspath', type: :class do + describe 'valid handling' do + %w{ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + X:/var/ůťƒ8 + X:/var/ネット + }.each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile } + end + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + "httds://notquiteright.org", + "/usr2/username/bin:/usr/local/bin:/usr/bin:.", + "C;//notright/here", + "C:noslashes", + "C:ネット", + "C:ůťƒ8" + ].each do |value| + describe value.inspect do + let(:params) {{ value: value }} + it { is_expected.to compile.and_raise_error(/parameter 'value' expects a match for Stdlib::Windowspath/) } + end + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/fixtures/dscacheutil/root b/environments/production/modules/stdlib/spec/fixtures/dscacheutil/root new file mode 100644 index 0000000..1e34519 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/dscacheutil/root @@ -0,0 +1,8 @@ +name: root +password: * +uid: 0 +gid: 0 +dir: /var/root +shell: /bin/bash +gecos: rawr Root + diff --git a/environments/production/modules/stdlib/spec/fixtures/lsuser/root b/environments/production/modules/stdlib/spec/fixtures/lsuser/root new file mode 100644 index 0000000..afd59ca --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/lsuser/root @@ -0,0 +1,2 @@ +#name:home +root:/root diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp new file mode 100644 index 0000000..d77f6bd --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolute_path.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Absolute_path type alias +class test::absolute_path( + Stdlib::Compat::Absolute_path $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp new file mode 100644 index 0000000..8321471 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/absolutepath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Absolutepath type. Not to be confused with Stdlib::Compat::Absolute_path. +class test::absolutepath( + Stdlib::Absolutepath $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/array.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/array.pp new file mode 100644 index 0000000..84b6a5b --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/array.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Array type alias +class test::array( + Stdlib::Compat::Array $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/bool.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/bool.pp new file mode 100644 index 0000000..ab5b5ea --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/bool.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Bool type alias +class test::bool( + Stdlib::Compat::Bool $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp new file mode 100644 index 0000000..bd26002 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ensure_resources.pp @@ -0,0 +1,4 @@ +# A helper class to test the ensure_resources function +class test::ensure_resources($resource_type, $title_hash, $attributes_hash) { + ensure_resources($resource_type, $title_hash, $attributes_hash) +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/float.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/float.pp new file mode 100644 index 0000000..03a603d --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/float.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Float type alias +class test::float( + Stdlib::Compat::Float $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/hash.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/hash.pp new file mode 100644 index 0000000..c243570 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/hash.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Hash type alias +class test::hash( + Stdlib::Compat::Hash $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp new file mode 100644 index 0000000..9d6b92d --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpsurl.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::HTTPSUrl type alias +class test::httpsurl( + Stdlib::HTTPSUrl $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp new file mode 100644 index 0000000..abf869e --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/httpurl.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::HTTPUrl type alias +class test::httpurl( + Stdlib::HTTPUrl $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/integer.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/integer.pp new file mode 100644 index 0000000..a4f26df --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/integer.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Integer type alias +class test::integer( + Stdlib::Compat::Integer $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp new file mode 100644 index 0000000..bbbd804 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ip_address.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ip_address type alias +class test::ip_address( + Stdlib::Compat::Ip_address $value, + ) { + notice("Success") + } diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp new file mode 100644 index 0000000..2e8022d --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv4.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ipv4 type alias +class test::ipv4( + Stdlib::Compat::Ipv4 $value, + ) { + notice("Success") + } diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp new file mode 100644 index 0000000..7912fd6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/ipv6.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Compat::Ipv6 type alias +class test::ipv6( + Stdlib::Compat::Ipv6 $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/numeric.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/numeric.pp new file mode 100644 index 0000000..2657ebc --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/numeric.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::Numeric type alias +class test::numeric( + Stdlib::Compat::Numeric $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/string.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/string.pp new file mode 100644 index 0000000..6508c70 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/string.pp @@ -0,0 +1,8 @@ +# Class to test the Stdlib::Compat::String type alias +class test::string( + Stdlib::Compat::String $value, + ) { + + notice("Success") + +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp new file mode 100644 index 0000000..9311109 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/unixpath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Unixpath type alias +class test::unixpath( + Stdlib::Unixpath $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp b/environments/production/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp new file mode 100644 index 0000000..af93ed3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/fixtures/test/manifests/windowspath.pp @@ -0,0 +1,6 @@ +# Class to test the Stdlib::Windowspath type alias +class test::windowspath( + Stdlib::Windowspath $value, + ) { + notice("Success") +} diff --git a/environments/production/modules/stdlib/spec/functions/abs_spec.rb b/environments/production/modules/stdlib/spec/functions/abs_spec.rb new file mode 100755 index 0000000..7d2257b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/abs_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'abs' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation in puppet3', :unless => RSpec.configuration.puppet_future do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + end + + describe 'signature validation in puppet4', :if => RSpec.configuration.puppet_future do + it { pending "the puppet 4 implementation"; is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params([]).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params({}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + end + + it { is_expected.to run.with_params(-34).and_return(34) } + it { is_expected.to run.with_params("-34").and_return(34) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params("34").and_return(34) } + it { is_expected.to run.with_params(-34.5).and_return(34.5) } + it { is_expected.to run.with_params("-34.5").and_return(34.5) } + it { is_expected.to run.with_params(34.5).and_return(34.5) } + it { is_expected.to run.with_params("34.5").and_return(34.5) } +end diff --git a/environments/production/modules/stdlib/spec/functions/any2array_spec.rb b/environments/production/modules/stdlib/spec/functions/any2array_spec.rb new file mode 100755 index 0000000..631657f --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/any2array_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe "any2array" do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_return([]) } + it { is_expected.to run.with_params(true).and_return([true]) } + it { is_expected.to run.with_params('one').and_return(['one']) } + it { is_expected.to run.with_params('one', 'two').and_return(['one', 'two']) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two']).and_return(['one', 'two']) } + it { is_expected.to run.with_params({}).and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key', 'value']) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key', 'value']) } + + it { is_expected.to run.with_params('‰').and_return(['‰']) } + it { is_expected.to run.with_params('竹').and_return(['竹']) } + it { is_expected.to run.with_params('Ü').and_return(['Ü']) } + it { is_expected.to run.with_params('∇').and_return(['∇']) } + it { is_expected.to run.with_params('€', '万', 'Ö', '♥', '割').and_return(['€', '万', 'Ö', '♥', '割']) } +end diff --git a/environments/production/modules/stdlib/spec/functions/any2bool_spec.rb b/environments/production/modules/stdlib/spec/functions/any2bool_spec.rb new file mode 100755 index 0000000..9d351ce --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/any2bool_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'any2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(true).and_return(true) } + it { is_expected.to run.with_params(false).and_return(false) } + + it { is_expected.to run.with_params('1.5').and_return(true) } + + describe 'when testing stringy values that mean "true"' do + [ 'TRUE','1', 't', 'y', 'true', 'yes'].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing stringy values that mean "false"' do + [ 'FALSE','', '0', 'f', 'n', 'false', 'no', 'undef', 'undefined', nil, :undef ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end + + describe 'when testing numeric values that mean "true"' do + [ 1,'1',1.5, '1.5'].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing numeric that mean "false"' do + [ -1, '-1', -1.5, '-1.5', '0', 0 ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end + + describe 'everything else returns true' do + [ [], {}, ['1'], [1], {:one => 1} ].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/assert_private_spec.rb b/environments/production/modules/stdlib/spec/functions/assert_private_spec.rb new file mode 100755 index 0000000..355e0dd --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/assert_private_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'assert_private' do + context 'when called from inside module' do + it "should not fail" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('foo') + + is_expected.to run.with_params() + end + end + + context "when called from private class" do + before :each do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + end + + it "should fail with a class error message" do + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('hostclass') + + is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Class foo::baz is private/) + end + + context "with an explicit failure message" do + it { is_expected.to run.with_params('failure message!').and_raise_error(Puppet::ParseError, /failure message!/) } + end + end + + context "when called from private definition" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('definition') + + is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Definition foo::baz is private/) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/base64_spec.rb b/environments/production/modules/stdlib/spec/functions/base64_spec.rb new file mode 100755 index 0000000..842a37a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/base64_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'base64' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /first argument must be one of/) } + it { is_expected.to run.with_params("encode", ["two"]).and_raise_error(Puppet::ParseError, /second argument must be a string/) } + it { is_expected.to run.with_params("encode", 2).and_raise_error(Puppet::ParseError, /second argument must be a string/) } + it { is_expected.to run.with_params("encode", "thestring", "three").and_raise_error(Puppet::ParseError, /third argument must be one of/) } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n", "strict").and_raise_error(ArgumentError) } + + it { is_expected.to run.with_params("encode", "thestring").and_return("dGhlc3RyaW5n\n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n").and_return("thestring") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "thestring", "default").and_return("dGhlc3RyaW5n\n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n", "default").and_return("thestring") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n\n", "default").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "thestring", "strict").and_return("dGhlc3RyaW5n") } + it { is_expected.to run.with_params("decode", "dGhlc3RyaW5n", "strict").and_return("thestring") } + + it { is_expected.to run.with_params("encode", "a very long string that will cause the base64 encoder to produce output with multiple lines").and_return("YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0\nIGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5l\ncw==\n") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0\nIGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5l\ncw==\n").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + + it { is_expected.to run.with_params("encode", "a very long string that will cause the base64 encoder to produce output with multiple lines", "strict").and_return("YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + it { is_expected.to run.with_params("decode", "YSB2ZXJ5IGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBjYXVzZSB0aGUgYmFzZTY0IGVuY29kZXIgdG8gcHJvZHVjZSBvdXRwdXQgd2l0aCBtdWx0aXBsZSBsaW5lcw==", "strict").and_return("a very long string that will cause the base64 encoder to produce output with multiple lines") } + + it { is_expected.to run.with_params("encode", "https://www.google.com.tw/?gws_rd=ssl#q=hello+world", "urlsafe").and_return("aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS50dy8_Z3dzX3JkPXNzbCNxPWhlbGxvK3dvcmxk") } + it { is_expected.to run.with_params("decode", "aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS50dy8_Z3dzX3JkPXNzbCNxPWhlbGxvK3dvcmxk", "urlsafe").and_return("https://www.google.com.tw/?gws_rd=ssl#q=hello+world") } + + it { is_expected.to run.with_params("encode", "https://github.com/puppetlabs/puppetlabs-stdlib/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+Add", "urlsafe").and_return("aHR0cHM6Ly9naXRodWIuY29tL3B1cHBldGxhYnMvcHVwcGV0bGFicy1zdGRsaWIvcHVsbHM_dXRmOD0lRTIlOUMlOTMmcT1pcyUzQXByK2lzJTNBb3BlbitBZGQ=") } + it { is_expected.to run.with_params("decode", "aHR0cHM6Ly9naXRodWIuY29tL3B1cHBldGxhYnMvcHVwcGV0bGFicy1zdGRsaWIvcHVsbHM_dXRmOD0lRTIlOUMlOTMmcT1pcyUzQXByK2lzJTNBb3BlbitBZGQ=", "urlsafe").and_return("https://github.com/puppetlabs/puppetlabs-stdlib/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+Add") } +end diff --git a/environments/production/modules/stdlib/spec/functions/basename_spec.rb b/environments/production/modules/stdlib/spec/functions/basename_spec.rb new file mode 100755 index 0000000..3e02b01 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/basename_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'basename' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext').and_return('file.ext') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext').and_return('file.ext') } + it { is_expected.to run.with_params('/path/to/a/file.ext', '.ext').and_return('file') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext', '.ext').and_return('file') } + it { is_expected.to run.with_params('scheme:///path/to/a/file.ext').and_return('file.ext') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('scheme:///√ạĺűē/竹.ext').and_return('竹.ext') } + it { is_expected.to run.with_params('ҝẽγ:/√ạĺűē/竹.ㄘ', '.ㄘ').and_return('竹') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/bool2num_spec.rb b/environments/production/modules/stdlib/spec/functions/bool2num_spec.rb new file mode 100755 index 0000000..e506859 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/bool2num_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe 'bool2num' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + + [ true, 'true', AlsoString.new('true') ].each do |truthy| + it { is_expected.to run.with_params(truthy).and_return(1) } + end + + [ false, 'false', AlsoString.new('false') ].each do |falsey| + it { is_expected.to run.with_params(falsey).and_return(0) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/bool2str_spec.rb b/environments/production/modules/stdlib/spec/functions/bool2str_spec.rb new file mode 100755 index 0000000..23a754b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/bool2str_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'bool2str' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + [ 'true', 'false', nil, :undef, ''].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError) } + end + it { is_expected.to run.with_params(true, 'yes', 'no', 'maybe').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true, 'maybe').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true, 0, 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(true).and_return("true") } + it { is_expected.to run.with_params(false).and_return("false") } + it { is_expected.to run.with_params(true, 'yes', 'no').and_return("yes") } + it { is_expected.to run.with_params(false, 'yes', 'no').and_return("no") } + +end diff --git a/environments/production/modules/stdlib/spec/functions/camelcase_spec.rb b/environments/production/modules/stdlib/spec/functions/camelcase_spec.rb new file mode 100755 index 0000000..c78aa62 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/camelcase_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'camelcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("abc").and_return("Abc") } + it { is_expected.to run.with_params("aa_bb_cc").and_return("AaBbCc") } + it { is_expected.to run.with_params("_aa__bb__cc_").and_return("AaBbCc") } + it { is_expected.to run.with_params("100").and_return("100") } + it { is_expected.to run.with_params("1_00").and_return("100") } + it { is_expected.to run.with_params("_").and_return("") } + it { is_expected.to run.with_params("").and_return("") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["abc", "aa_bb_cc"]).and_return(["Abc", "AaBbCc"]) } + it { is_expected.to run.with_params(["abc", 1, "aa_bb_cc"]).and_return(["Abc", 1, "AaBbCc"]) } +end diff --git a/environments/production/modules/stdlib/spec/functions/capitalize_spec.rb b/environments/production/modules/stdlib/spec/functions/capitalize_spec.rb new file mode 100755 index 0000000..7ce2e16 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/capitalize_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'capitalize' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("one").and_return("One") } + it { is_expected.to run.with_params("one two").and_return("One two") } + it { is_expected.to run.with_params("ONE TWO").and_return("One two") } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("One") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["one", "two"]).and_return(["One", "Two"]) } + it { is_expected.to run.with_params(["one", 1, "two"]).and_return(["One", 1, "Two"]) } +end diff --git a/environments/production/modules/stdlib/spec/functions/ceiling_spec.rb b/environments/production/modules/stdlib/spec/functions/ceiling_spec.rb new file mode 100755 index 0000000..567426f --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/ceiling_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'ceiling' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("foo").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params(-34).and_return(-34) } + it { is_expected.to run.with_params(33.1).and_return(34) } + it { is_expected.to run.with_params(-33.1).and_return(-33) } +end + diff --git a/environments/production/modules/stdlib/spec/functions/chomp_spec.rb b/environments/production/modules/stdlib/spec/functions/chomp_spec.rb new file mode 100755 index 0000000..56bd9b1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/chomp_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'chomp' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params("a", "b").and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("one").and_return("one") } + it { is_expected.to run.with_params("one\n").and_return("one") } + it { is_expected.to run.with_params("one\n\n").and_return("one\n") } + it { is_expected.to run.with_params(["one\n", "two", "three\n"]).and_return(["one", "two", "three"]) } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n\n")).and_return("one\n") } + it { is_expected.to run.with_params([AlsoString.new("one\n"), AlsoString.new("two"), "three\n"]).and_return(["one", "two", "three"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params("ůťƒ8\n\n").and_return("ůťƒ8\n") } + it { is_expected.to run.with_params("ネット\n\n").and_return("ネット\n") } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/chop_spec.rb b/environments/production/modules/stdlib/spec/functions/chop_spec.rb new file mode 100755 index 0000000..b70fc37 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/chop_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'chop' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params("a", "b").and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("one").and_return("on") } + it { is_expected.to run.with_params("one\n").and_return("one") } + it { is_expected.to run.with_params("one\n\n").and_return("one\n") } + it { is_expected.to run.with_params(["one\n", "two", "three\n"]).and_return(["one", "tw", "three"]) } + + it { is_expected.to run.with_params(AlsoString.new("one")).and_return("on") } + it { is_expected.to run.with_params(AlsoString.new("one\n")).and_return("one") } + it { is_expected.to run.with_params(AlsoString.new("one\n\n")).and_return("one\n") } + it { is_expected.to run.with_params([AlsoString.new("one\n"), AlsoString.new("two"), "three\n"]).and_return(["one", "tw", "three"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params("ůťƒ8\n\n").and_return("ůťƒ8\n") } + it { is_expected.to run.with_params("ネット\n\n").and_return("ネット\n") } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/clamp_spec.rb b/environments/production/modules/stdlib/spec/functions/clamp_spec.rb new file mode 100644 index 0000000..3e2fe7a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/clamp_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'clamp' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(12, 88, 71, 190).and_raise_error(Puppet::ParseError, /Wrong number of arguments, need three to clamp/) } + it { is_expected.to run.with_params('12string', 88, 15).and_raise_error(Puppet::ParseError, /Required explicit numeric/) } + it { is_expected.to run.with_params(1, 2, {'a' => 55}).and_raise_error(Puppet::ParseError, /The Hash type is not allowed/) } + it { is_expected.to run.with_params('24', [575, 187]).and_return(187) } + it { is_expected.to run.with_params([4, 3, '99']).and_return(4) } + it { is_expected.to run.with_params(16, 750, 88).and_return(88) } + it { is_expected.to run.with_params([3, 873], 73).and_return(73) } + it { is_expected.to run.with_params([4], 8, 75).and_return(8) } + it { is_expected.to run.with_params([6], [31], 9911).and_return(31) } +end diff --git a/environments/production/modules/stdlib/spec/functions/concat_spec.rb b/environments/production/modules/stdlib/spec/functions/concat_spec.rb new file mode 100755 index 0000000..6ab1b6e --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/concat_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'concat' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([1]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, [2]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([1], [2], [3]).and_return([1, 2, 3]) } + it { is_expected.to run.with_params(['1','2','3'],['4','5','6']).and_return(['1','2','3','4','5','6']) } + it { is_expected.to run.with_params(['1','2','3'],'4').and_return(['1','2','3','4']) } + it { is_expected.to run.with_params(['1','2','3'],[['4','5'],'6']).and_return(['1','2','3',['4','5'],'6']) } + it { is_expected.to run.with_params(['1','2'],['3','4'],['5','6']).and_return(['1','2','3','4','5','6']) } + it { is_expected.to run.with_params(['1','2'],'3','4',['5','6']).and_return(['1','2','3','4','5','6']) } + +context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params([{"a" => "b"}], {"c" => "d", "e" => "f"}).and_return([{"a" => "b"}, {"c" => "d", "e" => "f"}]) } + it { is_expected.to run.with_params(['ấ','β','©'],['đ','ể','文字列']).and_return(['ấ','β','©','đ','ể','文字列']) } +end + + it "should leave the original array intact" do + argument1 = ['1','2','3'] + original1 = argument1.dup + argument2 = ['4','5','6'] + original2 = argument2.dup + result = subject.call([argument1,argument2]) + expect(argument1).to eq(original1) + expect(argument2).to eq(original2) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/convert_base_spec.rb b/environments/production/modules/stdlib/spec/functions/convert_base_spec.rb new file mode 100644 index 0000000..8ab2284 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/convert_base_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'convert_base' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("asdf").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("asdf","moo","cow").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(["1"],"2").and_raise_error(Puppet::ParseError, /argument must be either a string or an integer/) } + it { is_expected.to run.with_params("1",["2"]).and_raise_error(Puppet::ParseError, /argument must be either a string or an integer/) } + it { is_expected.to run.with_params("1",1).and_raise_error(Puppet::ParseError, /base must be at least 2 and must not be greater than 36/) } + it { is_expected.to run.with_params("1",37).and_raise_error(Puppet::ParseError, /base must be at least 2 and must not be greater than 36/) } + + it "should raise a ParseError if argument 1 is a string that does not correspond to an integer in base 10" do + is_expected.to run.with_params("ten",6).and_raise_error(Puppet::ParseError, /argument must be an integer or a string corresponding to an integer in base 10/) + end + + it "should raise a ParseError if argument 2 is a string and does not correspond to an integer in base 10" do + is_expected.to run.with_params(100,"hex").and_raise_error(Puppet::ParseError, /argument must be an integer or a string corresponding to an integer in base 10/) + end + + it { is_expected.to run.with_params("11",'16').and_return('b') } + it { is_expected.to run.with_params("35",'36').and_return('z') } + it { is_expected.to run.with_params(5, 2).and_return('101') } +end diff --git a/environments/production/modules/stdlib/spec/functions/count_spec.rb b/environments/production/modules/stdlib/spec/functions/count_spec.rb new file mode 100755 index 0000000..3854cb8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/count_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'count' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("one").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params("one", "two").and_return(1) } + it { + pending("should actually be like this, and not like above") + is_expected.to run.with_params("one", "two").and_raise_error(ArgumentError) + } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(["one", "two", "three"]).and_return(3) } + it { is_expected.to run.with_params(["one", "two", "two"], "two").and_return(2) } + it { is_expected.to run.with_params(["one", nil, "two"]).and_return(2) } + it { is_expected.to run.with_params(["one", "", "two"]).and_return(2) } + it { is_expected.to run.with_params(["one", :undef, "two"]).and_return(2) } + + it { is_expected.to run.with_params(["ổņ℮", "ŧщộ", "three"]).and_return(3) } + it { is_expected.to run.with_params(["ổņ℮", "ŧщộ", "ŧщộ"], "ŧщộ").and_return(2) } + it { is_expected.to run.with_params(["ổņ℮", nil, "ŧщộ"]).and_return(2) } + it { is_expected.to run.with_params(["ổņ℮", :undef, "ŧщộ"]).and_return(2) } +end diff --git a/environments/production/modules/stdlib/spec/functions/deep_merge_spec.rb b/environments/production/modules/stdlib/spec/functions/deep_merge_spec.rb new file mode 100755 index 0000000..c91a07e --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/deep_merge_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe 'deep_merge' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, '2').and_raise_error(Puppet::ParseError, /unexpected argument type String/) } + it { is_expected.to run.with_params({}, 2).and_raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) } + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}, {}).and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, '').and_return({'key' => 'value'}) } + it { is_expected.to run.with_params({'key1' => 'value1'}, {'key2' => 'value2' }).and_return({'key1' => 'value1', 'key2' => 'value2'}) } + + describe 'when arguments have key collisions' do + it 'should prefer values from the last hash' do + is_expected.to run \ + .with_params( + {'key1' => 'value1', 'key2' => 'value2' }, + {'key2' => 'replacement_value', 'key3' => 'value3'}) \ + .and_return( + {'key1' => 'value1', 'key2' => 'replacement_value', 'key3' => 'value3'}) + end + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, {'key1' => 'value2'}, {'key1' => 'value3'}) \ + .and_return({'key1' => 'value3' }) + } + end + + describe 'when arguments have subhashes' do + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, {'key2' => 'value2', 'key3' => {'subkey1' => 'value4'}}) \ + .and_return( {'key1' => 'value1', 'key2' => 'value2', 'key3' => {'subkey1' => 'value4'}}) + } + it { is_expected.to run \ + .with_params({'key1' => {'subkey1' => 'value1'}}, {'key1' => {'subkey2' => 'value2'}}) \ + .and_return( {'key1' => {'subkey1' => 'value1', 'subkey2' => 'value2'}}) + } + it { is_expected.to run \ + .with_params({'key1' => {'subkey1' => {'subsubkey1' => 'value1'}}}, {'key1' => {'subkey1' => {'subsubkey1' => 'value2'}}}) \ + .and_return( {'key1' => {'subkey1' => {'subsubkey1' => 'value2'}}}) + } + end + + it 'should not change the original hashes' do + argument1 = { 'key1' => 'value1' } + original1 = argument1.dup + argument2 = { 'key2' => 'value2' } + original2 = argument2.dup + + subject.call([argument1, argument2]) + expect(argument1).to eq(original1) + expect(argument2).to eq(original2) + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({'ĸέỹ1' => 'ϋǻļủë1'}, {'この文字列' => '万' }).and_return({'ĸέỹ1' => 'ϋǻļủë1', 'この文字列' => '万'}) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/defined_with_params_spec.rb b/environments/production/modules/stdlib/spec/functions/defined_with_params_spec.rb new file mode 100755 index 0000000..a0db0b7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/defined_with_params_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'defined_with_params' do + describe 'when no resource is specified' do + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + end + describe 'when compared against a resource with no attributes' do + let :pre_condition do + 'user { "dan": }' + end + it { is_expected.to run.with_params('User[dan]', {}).and_return(true) } + it { is_expected.to run.with_params('User[bob]', {}).and_return(false) } + it { is_expected.to run.with_params('User[dan]', {'foo' => 'bar'}).and_return(false) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('User[ĵĭмოү]', {}).and_return(false) } + it { is_expected.to run.with_params('User[ポーラ]', {}).and_return(false) } + end + end + + describe 'when compared against a resource with attributes' do + let :pre_condition do + 'user { "dan": ensure => present, shell => "/bin/csh", managehome => false}' + end + it { is_expected.to run.with_params('User[dan]', {}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', '').and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'present'}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'present', 'managehome' => false}).and_return(true) } + it { is_expected.to run.with_params('User[dan]', {'ensure' => 'absent', 'managehome' => false}).and_return(false) } + end + + describe 'when passing undef values' do + let :pre_condition do + 'file { "/tmp/a": ensure => present }' + end + + it { is_expected.to run.with_params('File[/tmp/a]', {}).and_return(true) } + it { is_expected.to run.with_params('File[/tmp/a]', { 'ensure' => 'present', 'owner' => :undef }).and_return(true) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/delete_at_spec.rb b/environments/production/modules/stdlib/spec/functions/delete_at_spec.rb new file mode 100755 index 0000000..0471039 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/delete_at_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'delete_at' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(['one'], 'two').and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first two.") + is_expected.to run.with_params(['one'], 0, 1).and_raise_error(Puppet::ParseError) + } + + describe 'argument validation' do + it { is_expected.to run.with_params([0, 1, 2], 3).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params([0, 1, 2], 1).and_return([0, 2]) } + it { is_expected.to run.with_params([0, 1, 2], -1).and_return([0, 1]) } + it { is_expected.to run.with_params([0, 1, 2], -4).and_return([0, 1, 2]) } + it { is_expected.to run.with_params(["ƒờở", "βāř", "ьầż"], 1).and_return(["ƒờở", "ьầż"]) } + + + it "should leave the original array intact" do + argument = [1, 2, 3] + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/delete_regex_spec.rb b/environments/production/modules/stdlib/spec/functions/delete_regex_spec.rb new file mode 100755 index 0000000..f27a946 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/delete_regex_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'delete_regex' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two') } + it { is_expected.to run.with_params({}, 'two') } + it { is_expected.to run.with_params([], 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two', 'three', 'four').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + + describe 'deleting from an array' do + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['two', 'two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], '^t.*').and_return(['one']) } + it { is_expected.to run.with_params(['ab', 'b', 'c', 'b'], 'b').and_return(['ab', 'c']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'four').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'e').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['two', 'one', 'two', 'three', 'two'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['abracadabra'], 'abr').and_return(['abracadabra']) } + it { is_expected.to run.with_params(['abracadabra'], '^.*jimbob.*$').and_return(['abracadabra']) } + end + + describe 'deleting from an array' do + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, 'key').and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, 'key').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, 'key2') \ + .and_return( {'key1' => 'value1', 'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, ['key1', 'key2']) \ + .and_return( {'key3' => 'value3'}) + } + end + + it "should leave the original array intact" do + argument1 = ['one','two','three'] + original1 = argument1.dup + subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original hash intact" do + argument1 = {'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'} + original1 = argument1.dup + subject.call([argument1,'key2']) + expect(argument1).to eq(original1) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/delete_spec.rb b/environments/production/modules/stdlib/spec/functions/delete_spec.rb new file mode 100755 index 0000000..4e37865 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/delete_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe 'delete' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two') } + it { is_expected.to run.with_params([], 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + + describe 'deleting from an array' do + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['two', 'two'], 'two').and_return([]) } + it { is_expected.to run.with_params(['ab', 'b', 'c', 'b'], 'b').and_return(['ab', 'c']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'four').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'e').and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['two', 'one', 'two', 'three', 'two'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'two'], ['one', 'two']).and_return(['three']) } + it { is_expected.to run.with_params(['ồאּẻ', 'ŧẅơ', 'ŧңŗё℮', 'ŧẅơ'], ['ồאּẻ', 'ŧẅơ']).and_return(['ŧңŗё℮']) } + end + + describe 'deleting from a string' do + it { is_expected.to run.with_params('', '').and_return('') } + it { is_expected.to run.with_params('bar', '').and_return('bar') } + it { is_expected.to run.with_params('', 'bar').and_return('') } + it { is_expected.to run.with_params('bar', 'bar').and_return('') } + it { is_expected.to run.with_params('barbar', 'bar').and_return('') } + it { is_expected.to run.with_params('barfoobar', 'bar').and_return('foo') } + it { is_expected.to run.with_params('foobarbabarz', 'bar').and_return('foobaz') } + it { is_expected.to run.with_params('foobarbabarz', ['foo', 'bar']).and_return('baz') } + it { is_expected.to run.with_params('ƒōōβậяβậβậяź', ['ƒōō', 'βậя']).and_return('βậź') } + + it { is_expected.to run.with_params('barfoobar', ['barbar', 'foo']).and_return('barbar') } + it { is_expected.to run.with_params('barfoobar', ['foo', 'barbar']).and_return('') } + end + + describe 'deleting from an array' do + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({}, 'key').and_return({}) } + it { is_expected.to run.with_params({'key' => 'value'}, 'key').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, 'key2') \ + .and_return( {'key1' => 'value1', 'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'}, ['key1', 'key2']) \ + .and_return( {'key3' => 'value3'}) + } + it { is_expected.to run \ + .with_params({'ĸəұ1' => 'νãŀủĕ1', 'ĸəұ2' => 'νãŀủĕ2', 'ĸəұ3' => 'νãŀủĕ3'}, ['ĸəұ1', 'ĸəұ2']) \ + .and_return( {'ĸəұ3' => 'νãŀủĕ3'}) + } + end + + it "should leave the original array intact" do + argument1 = ['one','two','three'] + original1 = argument1.dup + result = subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original string intact" do + argument1 = 'onetwothree' + original1 = argument1.dup + result = subject.call([argument1,'two']) + expect(argument1).to eq(original1) + end + it "should leave the original hash intact" do + argument1 = {'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3'} + original1 = argument1.dup + result = subject.call([argument1,'key2']) + expect(argument1).to eq(original1) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/delete_undef_values_spec.rb b/environments/production/modules/stdlib/spec/functions/delete_undef_values_spec.rb new file mode 100755 index 0000000..c20cee2 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/delete_undef_values_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe 'delete_undef_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + + describe 'when deleting from an array' do + [ :undef, '', nil ].each do |undef_value| + describe "when undef is represented by #{undef_value.inspect}" do + before do + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == '' + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == nil + end + it { is_expected.to run.with_params([undef_value]).and_return([]) } + it { is_expected.to run.with_params(['one',undef_value,'two','three']).and_return(['one','two','three']) } + it { is_expected.to run.with_params(['ớņέ',undef_value,'ŧשּׁō','ŧħґëə']).and_return(['ớņέ','ŧשּׁō','ŧħґëə']) } + end + + it "should leave the original argument intact" do + argument = ['one',undef_value,'two'] + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end + end + + it { is_expected.to run.with_params(['undef']).and_return(['undef']) } + end + + describe 'when deleting from a hash' do + [ :undef, '', nil ].each do |undef_value| + describe "when undef is represented by #{undef_value.inspect}" do + before do + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == '' + pending("review behaviour when being passed undef as #{undef_value.inspect}") if undef_value == nil + end + it { is_expected.to run.with_params({'key' => undef_value}).and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'undef_key' => undef_value, 'key2' => 'value2'}) \ + .and_return({'key1' => 'value1', 'key2' => 'value2'}) + } + end + + it "should leave the original argument intact" do + argument = { 'key1' => 'value1', 'key2' => undef_value } + original = argument.dup + result = subject.call([argument,2]) + expect(argument).to eq(original) + end + end + + it { is_expected.to run.with_params({'key' => 'undef'}).and_return({'key' => 'undef'}) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/delete_values_spec.rb b/environments/production/modules/stdlib/spec/functions/delete_values_spec.rb new file mode 100755 index 0000000..329fa0b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/delete_values_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'delete_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + describe 'when the first argument is not a hash' do + it { is_expected.to run.with_params(1, 'two').and_raise_error(TypeError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(TypeError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(TypeError) } + end + + describe 'when deleting from a hash' do + it { is_expected.to run.with_params({}, 'value').and_return({}) } + it { is_expected.to run \ + .with_params({'key1' => 'value1'}, 'non-existing value') \ + .and_return({'key1' => 'value1'}) + } + it { is_expected.to run \ + .with_params({'ҝếỵ1 ' => 'νâĺūẹ1', 'ҝếỵ2' => 'value to delete'}, 'value to delete') \ + .and_return({'ҝếỵ1 ' => 'νâĺūẹ1'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'νǎŀữ℮ ťớ đêłểťė'}, 'νǎŀữ℮ ťớ đêłểťė') \ + .and_return({'key1' => 'value1'}) + } + it { is_expected.to run \ + .with_params({'key1' => 'value1', 'key2' => 'value to delete', 'key3' => 'value to delete'}, 'value to delete') \ + .and_return({'key1' => 'value1'}) + } + end + + it "should leave the original argument intact" do + argument = { 'key1' => 'value1', 'key2' => 'value2' } + original = argument.dup + result = subject.call([argument, 'value2']) + expect(argument).to eq(original) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/deprecation_spec.rb b/environments/production/modules/stdlib/spec/functions/deprecation_spec.rb new file mode 100644 index 0000000..bde4e89 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/deprecation_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.5.0') >= 0 + describe 'deprecation' do + before(:each) { + # this is to reset the strict variable to default + Puppet.settings[:strict] = :warning + } + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + + it 'should display a single warning' do + Puppet.expects(:warning).with(includes('heelo')) + is_expected.to run.with_params('key', 'heelo') + end + + it 'should display a single warning, despite multiple calls' do + Puppet.expects(:warning).with(includes('heelo')).once + is_expected.to run.with_params('key', 'heelo') + is_expected.to run.with_params('key', 'heelo') + end + + it 'should fail twice with message, with multiple calls. when strict= :error' do + Puppet.settings[:strict] = :error + Puppet.expects(:warning).with(includes('heelo')).never + is_expected.to run.with_params('key', 'heelo').and_raise_error(RuntimeError, /deprecation. key. heelo/) + is_expected.to run.with_params('key', 'heelo').and_raise_error(RuntimeError, /deprecation. key. heelo/) + end + + it 'should display nothing, despite multiple calls. strict= :off' do + Puppet.settings[:strict] = :off + Puppet.expects(:warning).with(includes('heelo')).never + is_expected.to run.with_params('key', 'heelo') + is_expected.to run.with_params('key', 'heelo') + end + + after(:all) { + # this is to reset the strict variable to default + Puppet.settings[:strict] = :warning + } + end +elsif Puppet.version.to_f < 4.0 + # Puppet version < 4 will use these tests. + describe 'deprecation' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + before(:all) do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + end + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it 'should display a single warning' do + scope.expects(:warning).with(includes('heelo')) + is_expected.to run.with_params('key', 'heelo') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/difference_spec.rb b/environments/production/modules/stdlib/spec/functions/difference_spec.rb new file mode 100755 index 0000000..0ae3689 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/difference_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'difference' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return([]) } + it { is_expected.to run.with_params(['one'], ['one']).and_return([]) } + it { is_expected.to run.with_params(['ớņέ'], ['']).and_return(['ớņέ']) } + it { is_expected.to run.with_params(['one'], []).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['ớņέ', 'ŧשּׁō', 'ŧħґëə', 2], ['ŧשּׁō', 'ŧħґëə']).and_return(['ớņέ', 2]) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three', 'four']).and_return(['one']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return(['1', '2', '3']) end +end diff --git a/environments/production/modules/stdlib/spec/functions/dig44_spec.rb b/environments/production/modules/stdlib/spec/functions/dig44_spec.rb new file mode 100644 index 0000000..a7b8a3d --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/dig44_spec.rb @@ -0,0 +1,131 @@ +require 'spec_helper' + +describe 'dig44' do + + let(:data) do + { + 'a' => { + 'g' => '2', + 'e' => [ + 'f0', + 'f1', + { + 'x' => { + 'y' => 'z' + } + }, + 'f3', + ] + }, + 'b' => true, + 'c' => false, + 'd' => '1', + 'e' => :undef, + 'f' => nil, + } + end + + let(:utf8_data) do + { + 'ẵ' => { + 'в' => [ + '©', + 'ĝ', + 'に', + ] + } + } + end + + context 'single values' do + it 'should exist' do + is_expected.not_to be_nil + end + + it 'should require two arguments' do + is_expected.to run.with_params().and_raise_error(ArgumentError) + end + + it 'should fail if the data is not a structure' do + is_expected.to run.with_params('test', []).and_raise_error(Puppet::Error) + end + + it 'should fail if the path is not an array' do + is_expected.to run.with_params({}, '').and_raise_error(Puppet::Error) + end + + it 'should return the value if the value is string' do + is_expected.to run.with_params(data, ['d'], 'default').and_return('1') + end + + it 'should return true if the value is true' do + is_expected.to run.with_params(data, ['b'], 'default').and_return(true) + end + + it 'should return false if the value is false' do + is_expected.to run.with_params(data, ['c'], 'default').and_return(false) + end + + it 'should return the default if the value is nil' do + is_expected.to run.with_params(data, ['f'], 'default').and_return('default') + end + + it 'should return the default if the value is :undef (same as nil)' do + is_expected.to run.with_params(data, ['e'], 'default').and_return('default') + end + + it 'should return the default if the path is not found' do + is_expected.to run.with_params(data, ['missing'], 'default').and_return('default') + end + end + + context 'structure values' do + + it 'should be able to extract a deeply nested hash value' do + is_expected.to run.with_params(data, %w(a g), 'default').and_return('2') + end + + it 'should return the default value if the path is too long' do + is_expected.to run.with_params(data, %w(a g c d), 'default').and_return('default') + end + + it 'should support an array index (number) in the path' do + is_expected.to run.with_params(data, ['a', 'e', 1], 'default').and_return('f1') + end + + it 'should support an array index (string) in the path' do + is_expected.to run.with_params(data, %w(a e 1), 'default').and_return('f1') + end + + it 'should return the default value if an array index is not a number' do + is_expected.to run.with_params(data, %w(a b c), 'default').and_return('default') + end + + it 'should return the default value if and index is out of array length' do + is_expected.to run.with_params(data, %w(a e 5), 'default').and_return('default') + end + + it 'should be able to path though both arrays and hashes' do + is_expected.to run.with_params(data, %w(a e 2 x y), 'default').and_return('z') + end + + it 'should return "nil" if value is not found and no default value is provided' do + is_expected.to run.with_params(data, %w(a 1)).and_return(nil) + end + end + + context 'Internationalization (i18N) values' do + + it 'should be able to return a unicode character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 0]).and_return('©') + end + + it 'should be able to return a utf8 character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 1]).and_return('ĝ') + end + + it 'should be able to return a double byte character' do + is_expected.to run.with_params(utf8_data, ['ẵ', 'в', 2]).and_return('に') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/dig_spec.rb b/environments/production/modules/stdlib/spec/functions/dig_spec.rb new file mode 100644 index 0000000..ad16fdd --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/dig_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'dig' do + + it "should exist" do + expect(Puppet::Parser::Functions.function("dig")).to eq("function_dig") + end + + it "should give a deprecation warning when called" do + scope.expects(:warning).with("dig() DEPRECATED: This function has been replaced in Puppet 4.5.0, please use dig44() for backwards compatibility or use the new version.") + scope.function_dig([{}, []]) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/dirname_spec.rb b/environments/production/modules/stdlib/spec/functions/dirname_spec.rb new file mode 100755 index 0000000..c494915 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/dirname_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe 'dirname' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('/path/to/a/file.ext').and_return('/path/to/a') } + it { is_expected.to run.with_params('relative_path/to/a/file.ext').and_return('relative_path/to/a') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('scheme:///√ạĺűē/竹.ext').and_return('scheme:///√ạĺűē') } + it { is_expected.to run.with_params('ҝẽγ:/√ạĺűē/竹.ㄘ').and_return('ҝẽγ:/√ạĺűē') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/dos2unix_spec.rb b/environments/production/modules/stdlib/spec/functions/dos2unix_spec.rb new file mode 100644 index 0000000..97abae7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/dos2unix_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'dos2unix' do + context 'Checking parameter validity' do + it { is_expected.not_to eq(nil) } + it do + is_expected.to run.with_params.and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params('one', 'two').and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + end + end + + context 'Converting from dos to unix format' do + sample_text = "Hello\r\nWorld\r\n" + desired_output = "Hello\nWorld\n" + + it 'should output unix format' do + should run.with_params(sample_text).and_return(desired_output) + end + end + + context 'Internationalization (i18N) values' do + sample_text_utf8 = "Ħ℮ļłǿ\r\nשׁөŕłđ\r\n" + desired_output_utf8 = "Ħ℮ļłǿ\nשׁөŕłđ\n" + + sample_text_doublebyte = "こんにちは\r\n世界\r\n" + desired_output_doublebyte = "こんにちは\n世界\n" + + it 'should output uft8 string' do + should run.with_params(sample_text_utf8).and_return(desired_output_utf8) + end + + it 'should output double byte string' do + should run.with_params(sample_text_doublebyte).and_return(desired_output_doublebyte) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/downcase_spec.rb b/environments/production/modules/stdlib/spec/functions/downcase_spec.rb new file mode 100755 index 0000000..c594560 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/downcase_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'downcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(100).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("abc").and_return("abc") } + it { is_expected.to run.with_params("Abc").and_return("abc") } + it { is_expected.to run.with_params("ABC").and_return("abc") } + + it { is_expected.to run.with_params(AlsoString.new("ABC")).and_return("abc") } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["ONE", "TWO"]).and_return(["one", "two"]) } + it { is_expected.to run.with_params(["One", 1, "Two"]).and_return(["one", 1, "two"]) } +end diff --git a/environments/production/modules/stdlib/spec/functions/empty_spec.rb b/environments/production/modules/stdlib/spec/functions/empty_spec.rb new file mode 100755 index 0000000..a3a25d6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/empty_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'empty' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params(0).and_return(false) } + it { is_expected.to run.with_params('').and_return(true) } + it { is_expected.to run.with_params('one').and_return(false) } + + it { is_expected.to run.with_params(AlsoString.new('')).and_return(true) } + it { is_expected.to run.with_params(AlsoString.new('one')).and_return(false) } + + it { is_expected.to run.with_params([]).and_return(true) } + it { is_expected.to run.with_params(['one']).and_return(false) } + + it { is_expected.to run.with_params({}).and_return(true) } + it { is_expected.to run.with_params({'key' => 'value'}).and_return(false) } +end diff --git a/environments/production/modules/stdlib/spec/functions/ensure_packages_spec.rb b/environments/production/modules/stdlib/spec/functions/ensure_packages_spec.rb new file mode 100755 index 0000000..6f94d72 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/ensure_packages_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'ensure_packages' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + } + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(["packagename", 1]).and_raise_error(Puppet::ParseError) + } + it { is_expected.to run.with_params("packagename") } + it { is_expected.to run.with_params(["packagename1", "packagename2"]) } + + context 'given a catalog with "package { puppet: ensure => absent }"' do + let(:pre_condition) { 'package { puppet: ensure => absent }' } + + describe 'after running ensure_package("facter")' do + before { subject.call(['facter']) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('puppet').with_ensure('absent') } + it { expect(lambda { catalogue }).to contain_package('facter').with_ensure('present') } + end + + describe 'after running ensure_package("facter", { "provider" => "gem" })' do + before { subject.call(['facter', { "provider" => "gem" }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('puppet').with_ensure('absent').without_provider() } + it { expect(lambda { catalogue }).to contain_package('facter').with_ensure('present').with_provider("gem") } + end + end + + context 'given hash of packages' do + before { subject.call([{"foo" => { "provider" => "rpm" }, "bar" => { "provider" => "gem" }}, { "ensure" => "present"}]) } + before { subject.call([{"パッケージ" => { "ensure" => "absent"}}]) } + before { subject.call([{"ρǻ¢κầģẻ" => { "ensure" => "absent"}}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_package('foo').with({'provider' => 'rpm', 'ensure' => 'present'}) } + it { expect(lambda { catalogue }).to contain_package('bar').with({'provider' => 'gem', 'ensure' => 'present'}) } + + context 'should run with UTF8 and double byte characters' do + it { expect(lambda { catalogue }).to contain_package('パッケージ').with({'ensure' => 'absent'}) } + it { expect(lambda { catalogue }).to contain_package('ρǻ¢κầģẻ').with({'ensure' => 'absent'}) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/ensure_resource_spec.rb b/environments/production/modules/stdlib/spec/functions/ensure_resource_spec.rb new file mode 100755 index 0000000..5366205 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/ensure_resource_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +describe 'ensure_resource' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /Must specify a type/) } + it { is_expected.to run.with_params('type').and_raise_error(ArgumentError, /Must specify a title/) } + if Puppet::Util::Package.versioncmp(Puppet.version, '4.6.0') >= 0 + it { is_expected.to run.with_params('type', 'title', {}, 'extras').and_raise_error(ArgumentError) } + else + it { is_expected.to run.with_params('type', 'title', {}, 'extras').and_raise_error(Puppet::ParseError) } + end + + it { + pending("should not accept numbers as arguments") + is_expected.to run.with_params(1,2,3).and_raise_error(Puppet::ParseError) + } + + context 'given an empty catalog' do + describe 'after running ensure_resource("user", "username1", {})' do + before { subject.call(['User', 'username1', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').without_ensure } + end + + describe 'after running ensure_resource("user", "username1", { gid => undef })' do + before { subject.call(['User', 'username1', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').without_ensure } + it { expect(lambda { catalogue }).to contain_user('username1').without_gid } + end + + describe 'after running ensure_resource("user", "username1", { ensure => present, gid => undef })' do + before { subject.call(['User', 'username1', { 'ensure' => 'present', 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username1').without_gid } + end + end + + context 'given a catalog with UTF8 chars' do + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", {})' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_ensure } + end + + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", { gid => undef })' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_ensure } + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_gid } + end + + describe 'after running ensure_resource("user", "Şắოрŀễ Ţëם", { ensure => present, gid => undef })' do + before { subject.call(['User', 'Şắოрŀễ Ţëם', { 'ensure' => 'present', 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('Şắოрŀễ Ţëם').without_gid } + end + end + + context 'given a catalog with "user { username1: ensure => present }"' do + let(:pre_condition) { 'user { username1: ensure => present }' } + + describe 'after running ensure_resource("user", "username1", {})' do + before { subject.call(['User', 'username1', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + end + + describe 'after running ensure_resource("user", "username2", {})' do + before { subject.call(['User', 'username2', {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username2').without_ensure } + end + + describe 'after running ensure_resource("user", "username1", { gid => undef })' do + before { subject.call(['User', 'username1', { 'gid' => :undef }]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + end + + describe 'after running ensure_resource("user", ["username1", "username2"], {})' do + before { subject.call(['User', ['username1', 'username2'], {}]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with_ensure('present') } + it { expect(lambda { catalogue }).to contain_user('username2').without_ensure } + end + + describe 'when providing already set params' do + let(:params) { { 'ensure' => 'present' } } + before { subject.call(['User', ['username2', 'username3'], params]) } + + # this lambda is required due to strangeness within rspec-puppet's expectation handling + it { expect(lambda { catalogue }).to contain_user('username1').with(params) } + it { expect(lambda { catalogue }).to contain_user('username2').with(params) } + end + + context 'when trying to add params' do + it { is_expected.to run \ + .with_params('User', 'username1', { 'ensure' => 'present', 'shell' => true }) \ + .and_raise_error(Puppet::Resource::Catalog::DuplicateResourceError, /User\[username1\] is already declared/) + } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/flatten_spec.rb b/environments/production/modules/stdlib/spec/functions/flatten_spec.rb new file mode 100755 index 0000000..b80f3c5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/flatten_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'flatten' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params([['one']]).and_return(['one']) } + it { is_expected.to run.with_params(["a","b","c","d","e","f","g"]).and_return(["a","b","c","d","e","f","g"]) } + it { is_expected.to run.with_params([["a","b",["c",["d","e"],"f","g"]]]).and_return(["a","b","c","d","e","f","g"]) } + it { is_expected.to run.with_params(["ã","β",["ĉ",["đ","ẽ","ƒ","ġ"]]]).and_return(["ã","β","ĉ","đ","ẽ","ƒ","ġ"]) } +end diff --git a/environments/production/modules/stdlib/spec/functions/floor_spec.rb b/environments/production/modules/stdlib/spec/functions/floor_spec.rb new file mode 100755 index 0000000..608c602 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/floor_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe 'floor' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params("foo").and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params(34).and_return(34) } + it { is_expected.to run.with_params(-34).and_return(-34) } + it { is_expected.to run.with_params(33.1).and_return(33) } + it { is_expected.to run.with_params(-33.1).and_return(-34) } +end diff --git a/environments/production/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb b/environments/production/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb new file mode 100644 index 0000000..861a59e --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/fqdn_rand_string_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'fqdn_rand_string' do + let(:default_charset) { %r{\A[a-zA-Z0-9]{100}\z} } + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1.5).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(-10).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("-10").and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("string").and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params([]).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params({}).and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, /second argument must be undef or a string/) } + it { is_expected.to run.with_params(100).and_return(default_charset) } + it { is_expected.to run.with_params("100").and_return(default_charset) } + it { is_expected.to run.with_params(100, nil).and_return(default_charset) } + it { is_expected.to run.with_params(100, '').and_return(default_charset) } + it { is_expected.to run.with_params(100, 'a').and_return(/\Aa{100}\z/) } + it { is_expected.to run.with_params(100, 'ab').and_return(/\A[ab]{100}\z/) } + it { is_expected.to run.with_params(100, 'ãβ').and_return(/\A[ãβ]{100}\z/) } + + it "provides the same 'random' value on subsequent calls for the same host" do + expect(fqdn_rand_string(10)).to eql(fqdn_rand_string(10)) + end + + it "considers the same host and same extra arguments to have the same random sequence" do + first_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"]) + second_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"]) + + expect(first_random).to eql(second_random) + end + + it "allows extra arguments to control the random value on a single host" do + first_random = fqdn_rand_string(10, :extra_identifier => [1, "different", "host"]) + second_different_random = fqdn_rand_string(10, :extra_identifier => [2, "different", "host"]) + + expect(first_random).not_to eql(second_different_random) + end + + it "should return different strings for different hosts" do + val1 = fqdn_rand_string(10, :host => "first.host.com") + val2 = fqdn_rand_string(10, :host => "second.host.com") + + expect(val1).not_to eql(val2) + end + + def fqdn_rand_string(max, args = {}) + host = args[:host] || '127.0.0.1' + charset = args[:charset] + extra = args[:extra_identifier] || [] + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn", {}).returns(host) + + function_args = [max] + if args.has_key?(:charset) or !extra.empty? + function_args << charset + end + function_args += extra + scope.function_fqdn_rand_string(function_args) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/fqdn_rotate_spec.rb b/environments/production/modules/stdlib/spec/functions/fqdn_rotate_spec.rb new file mode 100755 index 0000000..7c1038a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/fqdn_rotate_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe 'fqdn_rotate' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('ã').and_return('ã') } + + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + + it "should rotate a string and the result should be the same size" do + expect(fqdn_rotate("asdf").size).to eq(4) + end + + it "should rotate a string to give the same results for one host" do + val1 = fqdn_rotate("abcdefg", :host => 'one') + val2 = fqdn_rotate("abcdefg", :host => 'one') + expect(val1).to eq(val2) + end + + it "allows extra arguments to control the random rotation on a single host" do + val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "different", "host"]) + val2 = fqdn_rotate("abcdefg", :extra_identifier => [2, "different", "host"]) + expect(val1).not_to eq(val2) + end + + it "considers the same host and same extra arguments to have the same random rotation" do + val1 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"]) + val2 = fqdn_rotate("abcdefg", :extra_identifier => [1, "same", "host"]) + expect(val1).to eq(val2) + end + + it "should rotate a string to give different values on different hosts" do + val1 = fqdn_rotate("abcdefg", :host => 'one') + val2 = fqdn_rotate("abcdefg", :host => 'two') + expect(val1).not_to eq(val2) + end + + it "should accept objects which extend String" do + result = fqdn_rotate(AlsoString.new('asdf')) + expect(result).to eq('dfas') + end + + it "should use the Puppet::Util.deterministic_rand function" do + if Puppet::Util.respond_to?(:deterministic_rand) + Puppet::Util.expects(:deterministic_rand).with(44489829212339698569024999901561968770,4) + fqdn_rotate("asdf") + else + skip 'Puppet::Util#deterministic_rand not available' + end + end + + it "should not leave the global seed in a deterministic state" do + fqdn_rotate("asdf") + rand1 = rand() + fqdn_rotate("asdf") + rand2 = rand() + expect(rand1).not_to eql(rand2) + end + + def fqdn_rotate(value, args = {}) + host = args[:host] || '127.0.0.1' + extra = args[:extra_identifier] || [] + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn").returns(host) + + function_args = [value] + extra + scope.function_fqdn_rotate(function_args) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/fqdn_uuid_spec.rb b/environments/production/modules/stdlib/spec/functions/fqdn_uuid_spec.rb new file mode 100644 index 0000000..d0c30d6 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/fqdn_uuid_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'fqdn_uuid' do + + context "Invalid parameters" do + it { should run.with_params().and_raise_error(ArgumentError, /No arguments given$/) } + end + + context "given string" do + it { should run.with_params('puppetlabs.com').and_return('9c70320f-6815-5fc5-ab0f-debe68bf764c') } + it { should run.with_params('google.com').and_return('64ee70a4-8cc1-5d25-abf2-dea6c79a09c8') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/get_module_path_spec.rb b/environments/production/modules/stdlib/spec/functions/get_module_path_spec.rb new file mode 100755 index 0000000..a39e413 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/get_module_path_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe 'get_module_path' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Could not find module/) } + + class StubModule + attr_reader :path + def initialize(path) + @path = path + end + end + + describe 'when locating a module' do + let(:modulepath) { "/tmp/does_not_exist" } + let(:path_of_module_foo) { StubModule.new("/tmp/does_not_exist/foo") } + + before(:each) { Puppet[:modulepath] = modulepath } + + context 'in the default environment' do + before(:each) { Puppet::Module.expects(:find).with('foo', 'rp_env').returns(path_of_module_foo) } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + + context 'when the modulepath is a list' do + before(:each) { Puppet[:modulepath] = modulepath + 'tmp/something_else' } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + end + end + + context 'in a non-default default environment' do + let(:environment) { 'test' } + before(:each) { Puppet::Module.expects(:find).with('foo', 'test').returns(path_of_module_foo) } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + + context 'when the modulepath is a list' do + before(:each) { Puppet[:modulepath] = modulepath + 'tmp/something_else' } + + it { is_expected.to run.with_params('foo').and_return(path_of_module_foo.path) } + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/getparam_spec.rb b/environments/production/modules/stdlib/spec/functions/getparam_spec.rb new file mode 100755 index 0000000..522ed3b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/getparam_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'getparam' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /Must specify a reference/) } + it { is_expected.to run.with_params('User[one]').and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', 2).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', []).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + it { is_expected.to run.with_params('User[one]', {}).and_raise_error(ArgumentError, /Must specify name of a parameter/) } + + describe 'when compared against a user resource with no params' do + let(:pre_condition) { 'user { "one": }' } + + it { is_expected.to run.with_params('User[one]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[two]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[one]', 'shell').and_return('') } + end + + describe 'when compared against a user resource with params' do + let(:pre_condition) { 'user { "one": ensure => present, shell => "/bin/sh", managehome => false, }' } + + it { is_expected.to run.with_params('User[one]', 'ensure').and_return('present') } + it { is_expected.to run.with_params('User[two]', 'ensure').and_return('') } + it { is_expected.to run.with_params('User[one]', 'shell').and_return('/bin/sh') } + it { is_expected.to run.with_params('User[one]', 'managehome').and_return(false) } + end + + describe 'when compared against a user resource with UTF8 and double byte params' do + let(:pre_condition) { 'user { ["三", "ƒốưř"]: ensure => present }' } + + it { is_expected.to run.with_params('User[三]', 'ensure').and_return('present') } + it { is_expected.to run.with_params('User[ƒốưř]', 'ensure').and_return('present') } + + end +end diff --git a/environments/production/modules/stdlib/spec/functions/getvar_spec.rb b/environments/production/modules/stdlib/spec/functions/getvar_spec.rb new file mode 100755 index 0000000..55789d8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/getvar_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'getvar' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params('$::foo').and_return(nil) } + + context 'given variables in namespaces' do + let(:pre_condition) { + <<-'ENDofPUPPETcode' + class site::data { $foo = 'baz' } + include site::data + ENDofPUPPETcode + } + + it { is_expected.to run.with_params('site::data::foo').and_return('baz') } + it { is_expected.to run.with_params('::site::data::foo').and_return('baz') } + it { is_expected.to run.with_params('::site::data::bar').and_return(nil) } + end + + context 'given variables in namespaces' do + let(:pre_condition) { + <<-'ENDofPUPPETcode' + class site::info { $lock = 'ŧҺîš íš ắ śţřĭŋĝ' } + class site::new { $item = '万Ü€‰' } + include site::info + include site::new + ENDofPUPPETcode + } + + it { is_expected.to run.with_params('site::info::lock').and_return('ŧҺîš íš ắ śţřĭŋĝ') } + it { is_expected.to run.with_params('::site::new::item').and_return('万Ü€‰') } + end +end + diff --git a/environments/production/modules/stdlib/spec/functions/grep_spec.rb b/environments/production/modules/stdlib/spec/functions/grep_spec.rb new file mode 100755 index 0000000..d2152b1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/grep_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'grep' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("grep does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { + pending("grep does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params(1, 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['two']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 't(wo|hree)').and_return(['two', 'three']) } + it { is_expected.to run.with_params(['ờאּê', 'ţשּׂỡ', 'ţһŗəè'], 'ţ(שּׂỡ|һŗəè)').and_return(['ţשּׂỡ', 'ţһŗəè']) } +end diff --git a/environments/production/modules/stdlib/spec/functions/has_interface_with_spec.rb b/environments/production/modules/stdlib/spec/functions/has_interface_with_spec.rb new file mode 100755 index 0000000..7334d38 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/has_interface_with_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'has_interface_with' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + # We need to mock out the Facts so we can specify how we expect this function + # to behave on different platforms. + context "On Mac OS X Systems" do + let(:facts) { { :interfaces => 'lo0,gif0,stf0,en1,p2p0,fw0,en0,vmnet1,vmnet8,utun0' } } + it { is_expected.to run.with_params('lo0').and_return(true) } + it { is_expected.to run.with_params('lo').and_return(false) } + end + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :ipaddress => '10.0.0.1', + :ipaddress_lo => '127.0.0.1', + :ipaddress_eth0 => '10.0.0.1', + :muppet => 'kermit', + :muppet_lo => 'mspiggy', + :muppet_eth0 => 'kermit', + } + end + + it { is_expected.to run.with_params('lo').and_return(true) } + it { is_expected.to run.with_params('lo0').and_return(false) } + it { is_expected.to run.with_params('ipaddress', '127.0.0.1').and_return(true) } + it { is_expected.to run.with_params('ipaddress', '10.0.0.1').and_return(true) } + it { is_expected.to run.with_params('ipaddress', '8.8.8.8').and_return(false) } + it { is_expected.to run.with_params('muppet', 'kermit').and_return(true) } + it { is_expected.to run.with_params('muppet', 'mspiggy').and_return(true) } + it { is_expected.to run.with_params('muppet', 'bigbird').and_return(false) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/has_ip_address_spec.rb b/environments/production/modules/stdlib/spec/functions/has_ip_address_spec.rb new file mode 100755 index 0000000..42a5a79 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/has_ip_address_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'has_ip_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :ipaddress => '10.0.0.1', + :ipaddress_lo => '127.0.0.1', + :ipaddress_eth0 => '10.0.0.1', + } + end + + it { is_expected.to run.with_params('127.0.0.1').and_return(true) } + it { is_expected.to run.with_params('10.0.0.1').and_return(true) } + it { is_expected.to run.with_params('8.8.8.8').and_return(false) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/has_ip_network_spec.rb b/environments/production/modules/stdlib/spec/functions/has_ip_network_spec.rb new file mode 100755 index 0000000..57cf613 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/has_ip_network_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'has_ip_network' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context "On Linux Systems" do + let(:facts) do + { + :interfaces => 'eth0,lo', + :network_lo => '127.0.0.0', + :network_eth0 => '10.0.0.0', + } + end + + it { is_expected.to run.with_params('127.0.0.0').and_return(true) } + it { is_expected.to run.with_params('10.0.0.0').and_return(true) } + it { is_expected.to run.with_params('8.8.8.0').and_return(false) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/has_key_spec.rb b/environments/production/modules/stdlib/spec/functions/has_key_spec.rb new file mode 100755 index 0000000..0e0e1cc --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/has_key_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'has_key' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + it { is_expected.to run.with_params(1, "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + it { is_expected.to run.with_params([], "two").and_raise_error(Puppet::ParseError, /expects the first argument to be a hash/) } + + it { is_expected.to run.with_params({ 'key' => 'value' }, "key").and_return(true) } + it { is_expected.to run.with_params({}, "key").and_return(false) } + it { is_expected.to run.with_params({ 'key' => 'value'}, "not a key").and_return(false) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({ 'κéỳ ' => '٧ậļųể' }, "κéỳ ").and_return(true) } + it { is_expected.to run.with_params({ 'キー' => '٧ậļųể' }, "キー").and_return(true) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/hash_spec.rb b/environments/production/modules/stdlib/spec/functions/hash_spec.rb new file mode 100755 index 0000000..092474b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/hash_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'hash' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(['one']).and_raise_error(Puppet::ParseError, /Unable to compute/) } + it { is_expected.to run.with_params([]).and_return({}) } + it { is_expected.to run.with_params(['key1', 'value1']).and_return({ 'key1' => 'value1' }) } + it { is_expected.to run.with_params(['κ℮ұ1', '√āĺűẻ1']).and_return({ 'κ℮ұ1' => '√āĺűẻ1' }) } + it { is_expected.to run.with_params(['key1', 'value1', 'key2', 'value2']).and_return({ 'key1' => 'value1', 'key2' => 'value2' }) } +end diff --git a/environments/production/modules/stdlib/spec/functions/intersection_spec.rb b/environments/production/modules/stdlib/spec/functions/intersection_spec.rb new file mode 100755 index 0000000..ec368a5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/intersection_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'intersection' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', 'two', 'three').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return([]) } + it { is_expected.to run.with_params(['one'], []).and_return([]) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['ōŋể', 'ŧשợ', 'ţђŕẽё'], ['ŧשợ', 'ţђŕẽё']).and_return(['ŧשợ', 'ţђŕẽё']) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three', 'four']).and_return(['two', 'three']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return([]) end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_a_spec.rb b/environments/production/modules/stdlib/spec/functions/is_a_spec.rb new file mode 100644 index 0000000..2d6cb46 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_a_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +if ENV["FUTURE_PARSER"] == 'yes' + describe 'type_of' do + pending 'teach rspec-puppet to load future-only functions under 3.7.5' do + it { is_expected.not_to eq(nil) } + end + end +end + +if Puppet.version.to_f >= 4.0 + describe 'is_a' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('', '').and_raise_error(ArgumentError) } + + it 'succeeds when comparing a string and a string' do + is_expected.to run.with_params('hello world', String).and_return(true) + end + + it 'fails when comparing an integer and a string' do + is_expected.to run.with_params(5, String).and_return(false) + end + + it 'suceeds when comparing an UTF8 and double byte characters' do + is_expected.to run.with_params('このテキスト', String).and_return(true) + is_expected.to run.with_params('ŧћịś ŧêχŧ', String).and_return(true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_array_spec.rb b/environments/production/modules/stdlib/spec/functions/is_array_spec.rb new file mode 100755 index 0000000..e89f54b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_array_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'is_array' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([]).and_return(true) } + it { is_expected.to run.with_params(['one']).and_return(true) } + it { is_expected.to run.with_params([1]).and_return(true) } + it { is_expected.to run.with_params([{}]).and_return(true) } + it { is_expected.to run.with_params([[]]).and_return(true) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(['1.2.3.4']).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(['1.2.3.4']).and_return(true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_bool_spec.rb b/environments/production/modules/stdlib/spec/functions/is_bool_spec.rb new file mode 100755 index 0000000..d21345c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_bool_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'is_bool' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(true, false).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(true).and_return(true) } + it { is_expected.to run.with_params(false).and_return(true) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params([{}]).and_return(false) } + it { is_expected.to run.with_params([[]]).and_return(false) } + it { is_expected.to run.with_params([true]).and_return(false) } + it { is_expected.to run.with_params('true').and_return(false) } + it { is_expected.to run.with_params('false').and_return(false) } + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(true).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(false).and_return(true) + end + end +end + diff --git a/environments/production/modules/stdlib/spec/functions/is_domain_name_spec.rb b/environments/production/modules/stdlib/spec/functions/is_domain_name_spec.rb new file mode 100755 index 0000000..c1bf0e3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_domain_name_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'is_domain_name' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('.').and_return(true) } + it { is_expected.to run.with_params('com').and_return(true) } + it { is_expected.to run.with_params('com.').and_return(true) } + it { is_expected.to run.with_params('x.com').and_return(true) } + it { is_expected.to run.with_params('x.com.').and_return(true) } + it { is_expected.to run.with_params('foo.example.com').and_return(true) } + it { is_expected.to run.with_params('foo.example.com.').and_return(true) } + it { is_expected.to run.with_params('2foo.example.com').and_return(true) } + it { is_expected.to run.with_params('2foo.example.com.').and_return(true) } + it { is_expected.to run.with_params('www.2foo.example.com').and_return(true) } + it { is_expected.to run.with_params('www.2foo.example.com.').and_return(true) } + describe 'inputs with spaces' do + it { is_expected.to run.with_params('invalid domain').and_return(false) } + end + describe 'inputs with hyphens' do + it { is_expected.to run.with_params('foo-bar.example.com').and_return(true) } + it { is_expected.to run.with_params('foo-bar.example.com.').and_return(true) } + it { is_expected.to run.with_params('www.foo-bar.example.com').and_return(true) } + it { is_expected.to run.with_params('www.foo-bar.example.com.').and_return(true) } + it { is_expected.to run.with_params('-foo.example.com').and_return(false) } + it { is_expected.to run.with_params('-foo.example.com').and_return(false) } + end + # Values obtained from Facter values will be frozen strings + # in newer versions of Facter: + it { is_expected.to run.with_params('www.example.com'.freeze).and_return(true) } + describe 'top level domain must be alphabetic if there are multiple labels' do + it { is_expected.to run.with_params('2com').and_return(true) } + it { is_expected.to run.with_params('www.example.2com').and_return(false) } + end + describe 'IP addresses are not domain names' do + it { is_expected.to run.with_params('192.168.1.1').and_return(false) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_email_address_spec.rb b/environments/production/modules/stdlib/spec/functions/is_email_address_spec.rb new file mode 100755 index 0000000..8b7b358 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_email_address_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe 'is_email_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('bob@gmail.com').and_return(true) } + it { is_expected.to run.with_params('alice+puppetlabs.com@gmail.com').and_return(true) } + it { is_expected.to run.with_params('peter.parker@gmail.com').and_return(true) } + it { is_expected.to run.with_params('1.2.3@domain').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5@').and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } +end diff --git a/environments/production/modules/stdlib/spec/functions/is_float_spec.rb b/environments/production/modules/stdlib/spec/functions/is_float_spec.rb new file mode 100755 index 0000000..af3322e --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_float_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'is_float' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0.1, 0.2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'passing a string' do + it { is_expected.to run.with_params('0.1').and_return(true) } + it { is_expected.to run.with_params('1.0').and_return(true) } + it { is_expected.to run.with_params('1').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params('one 1.0').and_return(false) } + it { is_expected.to run.with_params('1.0 one').and_return(false) } + end + + describe 'passing numbers' do + it { is_expected.to run.with_params(0.1).and_return(true) } + it { is_expected.to run.with_params(1.0).and_return(true) } + it { is_expected.to run.with_params(1).and_return(false) } + end + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(2.2).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(1.0).and_return(true) + end + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/is_function_available.rb b/environments/production/modules/stdlib/spec/functions/is_function_available.rb new file mode 100755 index 0000000..44f08c0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_function_available.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'is_function_available' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('include').and_return(true) } + it { is_expected.to run.with_params('no_such_function').and_return(false) } +end diff --git a/environments/production/modules/stdlib/spec/functions/is_hash_spec.rb b/environments/production/modules/stdlib/spec/functions/is_hash_spec.rb new file mode 100755 index 0000000..c2599a0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_hash_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe 'is_hash' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params({}).and_return(true) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } +end diff --git a/environments/production/modules/stdlib/spec/functions/is_integer_spec.rb b/environments/production/modules/stdlib/spec/functions/is_integer_spec.rb new file mode 100755 index 0000000..b296830 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_integer_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe 'is_integer' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(3).and_return(true) } + it { is_expected.to run.with_params('3').and_return(true) } + it { is_expected.to run.with_params(-3).and_return(true) } + it { is_expected.to run.with_params('-3').and_return(true) } + + it { is_expected.to run.with_params(3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + it { is_expected.to run.with_params(-3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('0001234').and_return(false) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(50).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(50).and_return(true) + end + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/is_ip_address_spec.rb b/environments/production/modules/stdlib/spec/functions/is_ip_address_spec.rb new file mode 100755 index 0000000..39525d7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_ip_address_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'is_ip_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('1.2.3.4').and_return(true) } + it { is_expected.to run.with_params('1.2.3.255').and_return(true) } + it { is_expected.to run.with_params('1.2.3.256').and_return(false) } + it { is_expected.to run.with_params('1.2.3').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5').and_return(false) } + it { is_expected.to run.with_params('fe00::1').and_return(true) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74').and_return(true) } + it { is_expected.to run.with_params('FE80:0000:CD12:D123:E2F8:47FF:FE09:DD74').and_return(true) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:zzzz').and_return(false) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09').and_return(false) } + it { is_expected.to run.with_params('fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74:dd74').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params(1).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1.2.3.4').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('1.2.3.4').and_return(true) + end + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_ipv4_address_spec.rb b/environments/production/modules/stdlib/spec/functions/is_ipv4_address_spec.rb new file mode 100644 index 0000000..985260c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_ipv4_address_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'is_ipv4_address' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + SharedData::IPV4_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first).and_return(true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_ipv6_address_spec.rb b/environments/production/modules/stdlib/spec/functions/is_ipv6_address_spec.rb new file mode 100644 index 0000000..acd6a87 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_ipv6_address_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'is_ipv6_address' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) } + it { is_expected.to run.with_params('85a3:0000:0000:8a2e:0370:7334:100.100.100.100').and_return(true) } + it { is_expected.to run.with_params('1.2.3').and_return(false) } + it { is_expected.to run.with_params('1.2.3.4.5').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('2001:0db8:85a3:0000:0000:8a2e:0370:7334').and_return(true) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/is_mac_address_spec.rb b/environments/production/modules/stdlib/spec/functions/is_mac_address_spec.rb new file mode 100755 index 0000000..c1e33b8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_mac_address_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'is_mac_address' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('00:a0:1f:12:7f:a0').and_return(true) } + it { is_expected.to run.with_params('00:A0:1F:12:7F:A0').and_return(true) } + it { is_expected.to run.with_params('00:00:00:00:00:0g').and_return(false) } + it { is_expected.to run.with_params('').and_return(false) } + it { is_expected.to run.with_params('one').and_return(false) } + + context 'function can handle UTF8 and double byte characters' do + it { is_expected.to run.with_params('ƒốưř').and_return(false) } + it { is_expected.to run.with_params('三+').and_return(false) } + end + + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params(1).and_return(false) + } + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params({}).and_return(false) + } + it { + pending "should properly typecheck its arguments" + is_expected.to run.with_params([]).and_return(false) + } +end diff --git a/environments/production/modules/stdlib/spec/functions/is_numeric_spec.rb b/environments/production/modules/stdlib/spec/functions/is_numeric_spec.rb new file mode 100755 index 0000000..5962d8a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_numeric_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe 'is_numeric' do + + it { is_expected.not_to eq(nil) } + + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { is_expected.to run.with_params(3).and_return(true) } + it { is_expected.to run.with_params('3').and_return(true) } + it { is_expected.to run.with_params(-3).and_return(true) } + it { is_expected.to run.with_params('-3').and_return(true) } + + it { is_expected.to run.with_params(3.7).and_return(true) } + it { is_expected.to run.with_params('3.7').and_return(true) } + it { is_expected.to run.with_params(-3.7).and_return(true) } + it { is_expected.to run.with_params('3.7').and_return(true) } + + it { is_expected.to run.with_params('-342.2315e-12').and_return(true) } + + it { is_expected.to run.with_params('one').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('0001234').and_return(false) } + it { is_expected.to run.with_params(' - 1234').and_return(false) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(7).and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(7).and_return(true) + end + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/is_string_spec.rb b/environments/production/modules/stdlib/spec/functions/is_string_spec.rb new file mode 100755 index 0000000..e92f85c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/is_string_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'is_string' do + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + + it { is_expected.to run.with_params(3).and_return(false) } + it { is_expected.to run.with_params('3').and_return(false) } + it { is_expected.to run.with_params(-3).and_return(false) } + it { is_expected.to run.with_params('-3').and_return(false) } + + it { is_expected.to run.with_params(3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + it { is_expected.to run.with_params(-3.7).and_return(false) } + it { is_expected.to run.with_params('3.7').and_return(false) } + + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params([1]).and_return(false) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params(true).and_return(false) } + it { is_expected.to run.with_params(false).and_return(false) } + it { is_expected.to run.with_params('one').and_return(true) } + it { is_expected.to run.with_params('0001234').and_return(true) } + + context 'Checking for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('sponge').and_return(true) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('bob').and_return(true) + end + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/join_keys_to_values_spec.rb b/environments/production/modules/stdlib/spec/functions/join_keys_to_values_spec.rb new file mode 100755 index 0000000..0a2a50c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/join_keys_to_values_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'join_keys_to_values' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /Takes exactly two arguments/) } + it { is_expected.to run.with_params({}, '', '').and_raise_error(Puppet::ParseError, /Takes exactly two arguments/) } + it { is_expected.to run.with_params('one', '').and_raise_error(TypeError, /The first argument must be a hash/) } + it { is_expected.to run.with_params({}, 2).and_raise_error(TypeError, /The second argument must be a string/) } + + it { is_expected.to run.with_params({}, '').and_return([]) } + it { is_expected.to run.with_params({}, ':').and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }, '').and_return(['keyvalue']) } + it { is_expected.to run.with_params({ 'key' => 'value' }, ':').and_return(['key:value']) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params({ 'ҝẽγ' => '√ạĺűē' }, ':').and_return(['ҝẽγ:√ạĺűē']) } + it { is_expected.to run.with_params({ 'ҝẽγ' => '√ạĺűē' }, '万').and_return(['ҝẽγ万√ạĺűē']) } + end + + it { is_expected.to run.with_params({ 'key' => nil }, ':').and_return(['key:']) } + it 'should run join_keys_to_values(, ":") and return the proper array' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2' }, ':']) + expect(result.sort).to eq(['key1:value1', 'key2:value2'].sort) + end + it 'should run join_keys_to_values(, " ") and return the proper array' do + result = subject.call([{ 'key1' => 'value1', 'key2' => ['value2', 'value3'] }, ' ']) + expect(result.sort).to eq(['key1 value1', 'key2 value2', 'key2 value3'].sort) + end +end + diff --git a/environments/production/modules/stdlib/spec/functions/join_spec.rb b/environments/production/modules/stdlib/spec/functions/join_spec.rb new file mode 100755 index 0000000..98852d5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/join_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'join' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], '', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Requires array to work with/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /Requires string to work with/) } + + it { is_expected.to run.with_params([]).and_return('') } + it { is_expected.to run.with_params([], ':').and_return('') } + it { is_expected.to run.with_params(['one']).and_return('one') } + it { is_expected.to run.with_params(['one'], ':').and_return('one') } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return('onetwothree') } + it { is_expected.to run.with_params(['one', 'two', 'three'], ':').and_return('one:two:three') } + it { is_expected.to run.with_params(['ōŋể', 'ŧשợ', 'ţђŕẽё'], ':').and_return('ōŋể:ŧשợ:ţђŕẽё') } +end diff --git a/environments/production/modules/stdlib/spec/functions/keys_spec.rb b/environments/production/modules/stdlib/spec/functions/keys_spec.rb new file mode 100755 index 0000000..fc7d6d8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/keys_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'keys' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /Requires hash to work with/) } + it { is_expected.to run.with_params({}).and_return([]) } + it { is_expected.to run.with_params({ 'key' => 'value' }).and_return(['key']) } + it 'should return the array of keys' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2' }]) + expect(result).to match_array(['key1', 'key2']) + end + + it 'should run with UTF8 and double byte characters' do + result = subject.call([{ 'ҝểү' => '√ẳŀμệ', 'キー' => '値' }]) + expect(result).to match_array(['ҝểү', 'キー']) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/length.rb b/environments/production/modules/stdlib/spec/functions/length.rb new file mode 100755 index 0000000..d1ab003 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/length.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'length' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /'length' expects 1 argument, got none/) } + it { is_expected.to run.with_params([], 'extra').and_raise_error(ArgumentError, /'length' expects 1 argument, got 2/) } + it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, /expects a value of type String, Array, or Hash, got Integer/) } + it { is_expected.to run.with_params(true).and_raise_error(ArgumentError, /expects a value of type String, Array, or Hash, got Boolean/) } + it { is_expected.to run.with_params('1').and_return(1) } + it { is_expected.to run.with_params('1.0').and_return(3) } + it { is_expected.to run.with_params([]).and_return(0) } + it { is_expected.to run.with_params(['a']).and_return(1) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(3) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(4) } + + it { is_expected.to run.with_params({}).and_return(0) } + it { is_expected.to run.with_params({'1' => '2'}).and_return(1) } + it { is_expected.to run.with_params({'1' => '2', '4' => '4'}).and_return(2) } + it { is_expected.to run.with_params({'€' => '@', '竹' => 'ǿňè'}).and_return(2) } + + it { is_expected.to run.with_params('').and_return(0) } + it { is_expected.to run.with_params('a').and_return(1) } + it { is_expected.to run.with_params('abc').and_return(3) } + it { is_expected.to run.with_params('abcd').and_return(4) } + it { is_expected.to run.with_params('万').and_return(1) } + it { is_expected.to run.with_params('āβćđ').and_return(4) } + + context 'when using a class extending String' do + it 'should call its size method' do + value = AlsoString.new('asdfghjkl') + value.expects(:length).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/load_module_metadata_spec.rb b/environments/production/modules/stdlib/spec/functions/load_module_metadata_spec.rb new file mode 100755 index 0000000..9496fcb --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/load_module_metadata_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe 'load_module_metadata' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params("one", "two", "three").and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe "when calling with valid arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"name": "puppetlabs-stdlib"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"name": "puppetlabs-stdlib"}') + end + + context "when calling with valid utf8 and double byte character arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"ĭďèʼnţĩƒіểя": "ċơņťęאּť ỡƒ ţħíš - +この文字"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"ĭďèʼnţĩƒіểя": "ċơņťęאּť ỡƒ ţħíš - +この文字"}') + end + + it "should json parse the file" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(true) + allow(File).to receive(:read).with('C:/path/to/module/metadata.json').and_return('{"name": "spencer-science"}') + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(true) + allow(File).to receive(:read).with('/path/to/module/metadata.json').and_return('{"name": "spencer-science"}') + end + + result = subject.call(['science']) + expect(result['name']).to eq('spencer-science') + end + + it "should fail by default if there is no metadata.json" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(false) + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(false) + end + expect {subject.call(['science'])}.to raise_error(Puppet::ParseError) + end + + it "should return nil if user allows empty metadata.json" do + if Puppet::Util::Platform.windows? + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('C:/path/to/module/') + allow(File).to receive(:exists?).with('C:/path/to/module/metadata.json').and_return(false) + else + allow(scope).to receive(:function_get_module_path).with(['science']).and_return('/path/to/module/') + allow(File).to receive(:exists?).with('/path/to/module/metadata.json').and_return(false) + end + result = subject.call(['science', true]) + expect(result).to eq({}) + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/loadjson_spec.rb b/environments/production/modules/stdlib/spec/functions/loadjson_spec.rb new file mode 100644 index 0000000..dbef805 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/loadjson_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'loadjson' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + + describe "when calling with valid arguments" do + before :each do + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/, {:encoding=>"utf-8"}).and_return('{"name": "puppetlabs-stdlib"}') + allow(File).to receive(:read).with(/\/(stdlib|test)\/metadata.json/).and_return('{"name": "puppetlabs-stdlib"}') + end + + context 'when a non-existing file is specified' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesnotexist' + else + '/tmp/doesnotexist' + end + } + before { + allow(File).to receive(:exists?).with(filename).and_return(false).once + allow(PSON).to receive(:load).never + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + it { is_expected.to run.with_params(filename, {'đẽƒằưļŧ' => '٧ẵłựέ'}).and_return({'đẽƒằưļŧ' => '٧ẵłựέ'}) } + it { is_expected.to run.with_params(filename, {'デフォルト' => '値'}).and_return({'デフォルト' => '値'}) } + end + + context 'when an existing file is specified' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesexist' + else + '/tmp/doesexist' + end + } + let(:data) { { 'key' => 'value', 'ķęŷ' => 'νậŀųề', 'キー' => '値'} } + let(:json) { '{"key":"value", {"ķęŷ":"νậŀųề" }, {"キー":"値" }' } + before { + allow(File).to receive(:exists?).with(filename).and_return(true).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(PSON).to receive(:load).with(json).and_return(data).once + } + it { is_expected.to run.with_params(filename).and_return(data) } + end + + context 'when the file could not be parsed' do + let(:filename) { + if Puppet::Util::Platform.windows? + 'C:/tmp/doesexist' + else + '/tmp/doesexist' + end + } + let(:json) { '{"key":"value"}' } + before { + allow(File).to receive(:exists?).with(filename).and_return(true).once + allow(File).to receive(:read).with(filename).and_return(json).once + allow(PSON).to receive(:load).with(json).once.and_raise StandardError, 'Something terrible have happened!' + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/loadyaml_spec.rb b/environments/production/modules/stdlib/spec/functions/loadyaml_spec.rb new file mode 100755 index 0000000..e9428e3 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/loadyaml_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'loadyaml' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + + context 'when a non-existing file is specified' do + let(:filename) { '/tmp/doesnotexist' } + before { + File.expects(:exists?).with(filename).returns(false).once + YAML.expects(:load_file).never + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + it { is_expected.to run.with_params(filename, {'đẽƒằưļŧ' => '٧ẵłựέ'}).and_return({'đẽƒằưļŧ' => '٧ẵłựέ'}) } + it { is_expected.to run.with_params(filename, {'デフォルト' => '値'}).and_return({'デフォルト' => '値'}) } + end + + context 'when an existing file is specified' do + let(:filename) { '/tmp/doesexist' } + let(:data) { { 'key' => 'value', 'ķęŷ' => 'νậŀųề', 'キー' => '値'} } + before { + File.expects(:exists?).with(filename).returns(true).once + YAML.expects(:load_file).with(filename).returns(data).once + } + it { is_expected.to run.with_params(filename).and_return(data) } + end + + context 'when the file could not be parsed' do + let(:filename) { '/tmp/doesexist' } + before { + File.expects(:exists?).with(filename).returns(true).once + YAML.stubs(:load_file).with(filename).once.raises StandardError, 'Something terrible have happened!' + } + it { is_expected.to run.with_params(filename, {'default' => 'value'}).and_return({'default' => 'value'}) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/lstrip_spec.rb b/environments/production/modules/stdlib/spec/functions/lstrip_spec.rb new file mode 100755 index 0000000..a5a09ed --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/lstrip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'lstrip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params("\tone").and_return('one') } + it { is_expected.to run.with_params("\t one").and_return('one') } + it { is_expected.to run.with_params('one ').and_return('one ') } + it { is_expected.to run.with_params(' one ').and_return('one ') } + it { is_expected.to run.with_params(' one ').and_return('one ') } + it { is_expected.to run.with_params(' ǿňè ').and_return('ǿňè ') } + it { is_expected.to run.with_params("\tone ").and_return('one ') } + it { is_expected.to run.with_params("\t one ").and_return('one ') } + it { is_expected.to run.with_params("one \t").and_return("one \t") } + it { is_expected.to run.with_params(" one \t").and_return("one \t") } + it { is_expected.to run.with_params(" one \t").and_return("one \t") } + it { is_expected.to run.with_params("\tone \t").and_return("one \t") } + it { is_expected.to run.with_params("\t one \t").and_return("one \t") } + it { is_expected.to run.with_params(' o n e ').and_return('o n e ') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return('one ') } +end diff --git a/environments/production/modules/stdlib/spec/functions/max_spec.rb b/environments/production/modules/stdlib/spec/functions/max_spec.rb new file mode 100755 index 0000000..66fb0c8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/max_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'max' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(1, 2).and_return(2) } + it { is_expected.to run.with_params(1, 2, 3).and_return(3) } + it { is_expected.to run.with_params(3, 2, 1).and_return(3) } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params('one', 'two').and_return('two') } + it { is_expected.to run.with_params('one', 'two', 'three').and_return('two') } + it { is_expected.to run.with_params('three', 'two', 'one').and_return('two') } + + describe 'implementation artifacts' do + it { is_expected.to run.with_params(1, 'one').and_return('one') } + it { is_expected.to run.with_params('1', 'one').and_return('one') } + it { is_expected.to run.with_params('1.3e1', '1.4e0').and_return('1.4e0') } + it { is_expected.to run.with_params(1.3e1, 1.4e0).and_return(1.3e1) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/member_spec.rb b/environments/production/modules/stdlib/spec/functions/member_spec.rb new file mode 100755 index 0000000..8988632 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/member_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'member' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], [], []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([], '').and_return(false) } + it { is_expected.to run.with_params([], ['']).and_return(false) } + it { is_expected.to run.with_params([''], '').and_return(true) } + it { is_expected.to run.with_params([''], ['']).and_return(true) } + it { is_expected.to run.with_params([], 'one').and_return(false) } + it { is_expected.to run.with_params([], ['one']).and_return(false) } + it { is_expected.to run.with_params(['one'], 'one').and_return(true) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(true) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four'], ['four', 'two']).and_return(true) } + it { is_expected.to run.with_params(['ọאּẹ', 'ŧẅồ', 'ţҺŗęē', 'ƒơџŕ'], ['ƒơџŕ', 'ŧẅồ']).and_return(true) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four'], ['four', 'five']).and_return(false) } + it { is_expected.to run.with_params(['ọאּẹ', 'ŧẅồ', 'ţҺŗęē', 'ƒơџŕ'], ['ƒơџŕ', 'ƒί√ə']).and_return(false) } +end diff --git a/environments/production/modules/stdlib/spec/functions/merge_spec.rb b/environments/production/modules/stdlib/spec/functions/merge_spec.rb new file mode 100755 index 0000000..7b53363 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/merge_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'merge' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params({}, 'two').and_raise_error(Puppet::ParseError, /unexpected argument type String/) } + it { is_expected.to run.with_params({}, 1).and_raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) } + it { + pending 'should not special case this' + is_expected.to run.with_params({}).and_return({}) + } + it { is_expected.to run.with_params({}, {}).and_return({}) } + it { is_expected.to run.with_params({}, {}, {}).and_return({}) } + describe 'should accept empty strings as puppet undef' do + it { is_expected.to run.with_params({}, '').and_return({}) } + end + it { is_expected.to run.with_params({ 'key' => 'value' }, {}).and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({}, { 'key' => 'value' }).and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({ 'key' => 'value1' }, { 'key' => 'value2' }).and_return({ 'key' => 'value2' }) } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1' }, { 'key2' => 'value2' }, { 'key3' => 'value3' }) \ + .and_return({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }) + } +end diff --git a/environments/production/modules/stdlib/spec/functions/min_spec.rb b/environments/production/modules/stdlib/spec/functions/min_spec.rb new file mode 100755 index 0000000..c840a72 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/min_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'min' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(1, 2).and_return(1) } + it { is_expected.to run.with_params(1, 2, 3).and_return(1) } + it { is_expected.to run.with_params(3, 2, 1).and_return(1) } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('one', 'two', 'three').and_return('one') } + it { is_expected.to run.with_params('three', 'two', 'one').and_return('one') } + + describe 'implementation artifacts' do + it { is_expected.to run.with_params(1, 'one').and_return(1) } + it { is_expected.to run.with_params('1', 'one').and_return('1') } + it { is_expected.to run.with_params('1.3e1', '1.4e0').and_return('1.3e1') } + it { is_expected.to run.with_params(1.3e1, 1.4e0).and_return(1.4e0) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/num2bool_spec.rb b/environments/production/modules/stdlib/spec/functions/num2bool_spec.rb new file mode 100755 index 0000000..494afff --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/num2bool_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'num2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('abc').and_raise_error(Puppet::ParseError, /does not look like a number/) } + it { is_expected.to run.with_params(1).and_return(true) } + it { is_expected.to run.with_params('1').and_return(true) } + it { is_expected.to run.with_params(1.5).and_return(true) } + it { is_expected.to run.with_params('1.5').and_return(true) } + it { is_expected.to run.with_params(-1).and_return(false) } + it { is_expected.to run.with_params('-1').and_return(false) } + it { is_expected.to run.with_params(-1.5).and_return(false) } + it { is_expected.to run.with_params('-1.5').and_return(false) } + it { is_expected.to run.with_params(0).and_return(false) } + it { is_expected.to run.with_params('0').and_return(false) } + it { is_expected.to run.with_params([]).and_return(false) } + it { is_expected.to run.with_params('[]').and_raise_error(Puppet::ParseError, /does not look like a number/) } + it { is_expected.to run.with_params({}).and_return(false) } + it { is_expected.to run.with_params('{}').and_raise_error(Puppet::ParseError, /does not look like a number/) } +end diff --git a/environments/production/modules/stdlib/spec/functions/parsejson_spec.rb b/environments/production/modules/stdlib/spec/functions/parsejson_spec.rb new file mode 100755 index 0000000..7b07e49 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/parsejson_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe 'parsejson' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should raise an error if called without any arguments' do + is_expected.to run.with_params(). + and_raise_error(/wrong number of arguments/i) + end + + context 'with correct JSON data' do + + it 'should be able to parse JSON data with a Hash' do + is_expected.to run.with_params('{"a":"1","b":"2"}'). + and_return({'a'=>'1', 'b'=>'2'}) + end + + it 'should be able to parse JSON data with an Array' do + is_expected.to run.with_params('["a","b","c"]'). + and_return(['a', 'b', 'c']) + end + + it 'should be able to parse empty JSON values' do + is_expected.to run.with_params('[]'). + and_return([]) + is_expected.to run.with_params('{}'). + and_return({}) + end + + it 'should be able to parse JSON data with a mixed structure' do + is_expected.to run.with_params('{"a":"1","b":2,"c":{"d":[true,false]}}'). + and_return({'a' =>'1', 'b' => 2, 'c' => { 'd' => [true, false] } }) + end + + it 'should be able to parse JSON data with a UTF8 and double byte characters' do + is_expected.to run.with_params('{"×":"これ","ý":"記号","です":{"©":["Á","ß"]}}'). + and_return({'×' =>'これ', 'ý' => '記号', 'です' => { '©' => ['Á', 'ß'] } }) + end + + it 'should not return the default value if the data was parsed correctly' do + is_expected.to run.with_params('{"a":"1"}', 'default_value'). + and_return({'a' => '1'}) + end + + end + + context 'with incorrect JSON data' do + it 'should raise an error with invalid JSON and no default' do + is_expected.to run.with_params(''). + and_raise_error(PSON::ParserError) + end + + it 'should support a structure for a default value' do + is_expected.to run.with_params('', {'a' => '1'}). + and_return({'a' => '1'}) + end + + ['', 1, 1.2, nil, true, false, [], {}, :yaml].each do |value| + it "should return the default value for an incorrect #{value.inspect} (#{value.class}) parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/parseyaml_spec.rb b/environments/production/modules/stdlib/spec/functions/parseyaml_spec.rb new file mode 100755 index 0000000..c2a138c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/parseyaml_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe 'parseyaml' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should raise an error if called without any arguments' do + is_expected.to run.with_params(). + and_raise_error(/wrong number of arguments/i) + end + + context 'with correct YAML data' do + it 'should be able to parse a YAML data with a String' do + is_expected.to run.with_params('--- just a string'). + and_return('just a string') + is_expected.to run.with_params('just a string'). + and_return('just a string') + end + + it 'should be able to parse YAML data with a Hash' do + is_expected.to run.with_params("---\na: '1'\nb: '2'\n"). + and_return({'a' => '1', 'b' => '2'}) + end + + it 'should be able to parse YAML data with an Array' do + is_expected.to run.with_params("---\n- a\n- b\n- c\n"). + and_return(['a', 'b', 'c']) + end + + it 'should be able to parse YAML data with a mixed structure' do + is_expected.to run.with_params("---\na: '1'\nb: 2\nc:\n d:\n - :a\n - true\n - false\n"). + and_return({'a' => '1', 'b' => 2, 'c' => {'d' => [:a, true, false]}}) + end + + it 'should be able to parse YAML data with a UTF8 and double byte characters' do + is_expected.to run.with_params("---\na: ×\nこれ: 記号\nです:\n ©:\n - Á\n - ß\n"). + and_return({"a"=>"×", "これ"=>"記号", "です"=>{"©"=>["Á", "ß"]} }) + end + + it 'should not return the default value if the data was parsed correctly' do + is_expected.to run.with_params("---\na: '1'\n", 'default_value'). + and_return({'a' => '1'}) + end + + end + + context 'on a modern ruby', :unless => RUBY_VERSION == '1.8.7' do + it 'should raise an error with invalid YAML and no default' do + is_expected.to run.with_params('["one"'). + and_raise_error(Psych::SyntaxError) + end + end + + context 'when running on ruby 1.8.7, which does not have Psych', :if => RUBY_VERSION == '1.8.7' do + it 'should raise an error with invalid YAML and no default' do + is_expected.to run.with_params('["one"'). + and_raise_error(ArgumentError) + end + end + + context 'with incorrect YAML data' do + it 'should support a structure for a default value' do + is_expected.to run.with_params('', {'a' => '1'}). + and_return({'a' => '1'}) + end + + [1, 1.2, nil, true, false, [], {}, :yaml].each do |value| + it "should return the default value for an incorrect #{value.inspect} (#{value.class}) parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + ['---', '...', '*8', ''].each do |value| + it "should return the default value for an incorrect #{value.inspect} string parameter" do + is_expected.to run.with_params(value, 'default_value'). + and_return('default_value') + end + end + end + + end + +end diff --git a/environments/production/modules/stdlib/spec/functions/pick_default_spec.rb b/environments/production/modules/stdlib/spec/functions/pick_default_spec.rb new file mode 100755 index 0000000..a7ffc86 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/pick_default_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'pick_default' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::Error, /Must receive at least one argument/) } + + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('ớņệ', 'ťωơ').and_return('ớņệ') } + it { is_expected.to run.with_params('', 'two').and_return('two') } + it { is_expected.to run.with_params(:undef, 'two').and_return('two') } + it { is_expected.to run.with_params(:undefined, 'two').and_return('two') } + it { is_expected.to run.with_params(nil, 'two').and_return('two') } + + [ '', :undef, :undefined, nil, {}, [], 1, 'default' ].each do |value| + describe "when providing #{value.inspect} as default" do + it { is_expected.to run.with_params('one', value).and_return('one') } + it { is_expected.to run.with_params('ớņệ', value).and_return('ớņệ') } + it { is_expected.to run.with_params([], value).and_return([]) } + it { is_expected.to run.with_params({}, value).and_return({}) } + it { is_expected.to run.with_params(value, value).and_return(value) } + it { is_expected.to run.with_params(:undef, value).and_return(value) } + it { is_expected.to run.with_params(:undefined, value).and_return(value) } + it { is_expected.to run.with_params(nil, value).and_return(value) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/pick_spec.rb b/environments/production/modules/stdlib/spec/functions/pick_spec.rb new file mode 100755 index 0000000..438553b --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/pick_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'pick' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /must receive at least one non empty value/) } + it { is_expected.to run.with_params('', nil, :undef, :undefined).and_raise_error(Puppet::ParseError, /must receive at least one non empty value/) } + it { is_expected.to run.with_params('one', 'two').and_return('one') } + it { is_expected.to run.with_params('', 'two').and_return('two') } + it { is_expected.to run.with_params(:undef, 'two').and_return('two') } + it { is_expected.to run.with_params(:undefined, 'two').and_return('two') } + it { is_expected.to run.with_params(nil, 'two').and_return('two') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(nil, 'このテキスト').and_return('このテキスト') } + it { is_expected.to run.with_params('', 'ŝẳмрłề џţƒ8 ţẽם', 'このテキスト').and_return('ŝẳмрłề џţƒ8 ţẽם') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/prefix_spec.rb b/environments/production/modules/stdlib/spec/functions/prefix_spec.rb new file mode 100755 index 0000000..5510c58 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/prefix_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'prefix' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], 'a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /expected first argument to be an Array or a Hash/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /expected second argument to be a String/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one', 2]).and_return(['one', '2']) } + it { is_expected.to run.with_params(['ớņệ', 2]).and_return(['ớņệ', '2']) } + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([''], '').and_return(['']) } + it { is_expected.to run.with_params(['one'], 'pre').and_return(['preone']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'pre').and_return(['preone', 'pretwo', 'prethree']) } + it { is_expected.to run.with_params({}).and_return({}) } + it { is_expected.to run.with_params({ 'key1' => 'value1', 2 => 3}).and_return({ 'key1' => 'value1', '2' => 3 }) } + it { is_expected.to run.with_params({}, '').and_return({}) } + it { is_expected.to run.with_params({ 'key' => 'value' }, '').and_return({ 'key' => 'value' }) } + it { is_expected.to run.with_params({ 'key' => 'value' }, 'pre').and_return({ 'prekey' => 'value' }) } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }, 'pre') \ + .and_return({ 'prekey1' => 'value1', 'prekey2' => 'value2', 'prekey3' => 'value3' }) + } +end diff --git a/environments/production/modules/stdlib/spec/functions/private_spec.rb b/environments/production/modules/stdlib/spec/functions/private_spec.rb new file mode 100644 index 0000000..a13be64 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/private_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'private' do + it 'should issue a warning' do + scope.expects(:warning).with("private() DEPRECATED: This function will cease to function on Puppet 4; please use assert_private() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + begin + subject.call [] + rescue + # ignore this + end + end + + context "when called from inside module" do + it "should not fail" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('foo') + expect { + subject.call [] + }.not_to raise_error + end + end + + context "with an explicit failure message" do + it "prints the failure message on error" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + expect { + subject.call ['failure message!'] + }.to raise_error Puppet::ParseError, /failure message!/ + end + end + + context "when called from private class" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('hostclass') + expect { + subject.call [] + }.to raise_error Puppet::ParseError, /Class foo::baz is private/ + end + end + + context "when called from private definition" do + it "should fail with a class error message" do + scope.expects(:lookupvar).with('module_name').returns('foo') + scope.expects(:lookupvar).with('caller_module_name').returns('bar') + scope.source.expects(:name).returns('foo::baz') + scope.source.expects(:type).returns('definition') + expect { + subject.call [] + }.to raise_error Puppet::ParseError, /Definition foo::baz is private/ + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/pw_hash_spec.rb b/environments/production/modules/stdlib/spec/functions/pw_hash_spec.rb new file mode 100644 index 0000000..df5348c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/pw_hash_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe 'pw_hash' do + + it { is_expected.not_to eq(nil) } + + context 'when there are less than 3 arguments' do + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password').and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 'sha-256').and_raise_error(ArgumentError, /wrong number of arguments/i) } + end + + context 'when there are more than 3 arguments' do + it { is_expected.to run.with_params('password', 'sha-256', 'salt', 'extra').and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 'sha-256', 'salt', 'extra', 'extra').and_raise_error(ArgumentError, /wrong number of arguments/i) } + end + + context 'when the first argument is not a string' do + it { is_expected.to run.with_params([], 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params({}, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params(1, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + it { is_expected.to run.with_params(true, 'sha-256', 'salt').and_raise_error(ArgumentError, /first argument must be a string/) } + end + + context 'when the first argument is undefined' do + it { is_expected.to run.with_params('', 'sha-256', 'salt').and_return(nil) } + it { is_expected.to run.with_params(nil, 'sha-256', 'salt').and_return(nil) } + end + + context 'when the second argument is not a string' do + it { is_expected.to run.with_params('password', [], 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', {}, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', 1, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params('password', true, 'salt').and_raise_error(ArgumentError, /second argument must be a string/) } + end + + context 'when the second argument is not one of the supported hashing algorithms' do + it { is_expected.to run.with_params('password', 'no such algo', 'salt').and_raise_error(ArgumentError, /is not a valid hash type/) } + end + + context 'when the third argument is not a string' do + it { is_expected.to run.with_params('password', 'sha-256', []).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', {}).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', 1).and_raise_error(ArgumentError, /third argument must be a string/) } + it { is_expected.to run.with_params('password', 'sha-256', true).and_raise_error(ArgumentError, /third argument must be a string/) } + end + + context 'when the third argument is empty' do + it { is_expected.to run.with_params('password', 'sha-512', '').and_raise_error(ArgumentError, /third argument must not be empty/) } + end + + context 'when the third argument contains invalid characters' do + it { is_expected.to run.with_params('password', 'sha-512', 'one%').and_raise_error(ArgumentError, /characters in salt must be in the set/) } + end + + context 'when running on a platform with a weak String#crypt implementation' do + before(:each) { allow_any_instance_of(String).to receive(:crypt).with('$1$1').and_return('a bad hash') } + + it { is_expected.to run.with_params('password', 'sha-512', 'salt').and_raise_error(Puppet::ParseError, /system does not support enhanced salts/) } + end + + if RUBY_PLATFORM == 'java' or 'test'.crypt('$1$1') == '$1$1$Bp8CU9Oujr9SSEw53WV6G.' + describe "on systems with enhanced salts support" do + it { is_expected.to run.with_params('password', 'md5', 'salt').and_return('$1$salt$qJH7.N4xYta3aEG/dfqo/0') } + it { is_expected.to run.with_params('password', 'sha-256', 'salt').and_return('$5$salt$Gcm6FsVtF/Qa77ZKD.iwsJlCVPY0XSMgLJL0Hnww/c1') } + it { is_expected.to run.with_params('password', 'sha-512', 'salt').and_return('$6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g.') } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/range_spec.rb b/environments/production/modules/stdlib/spec/functions/range_spec.rb new file mode 100755 index 0000000..ca569d5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/range_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe 'range' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation in puppet3', :unless => RSpec.configuration.puppet_future do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the third.") + is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('1..2..3').and_raise_error(Puppet::ParseError, /Unable to compute range/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /Unknown range format/i) } + end + + describe 'signature validation in puppet4', :if => RSpec.configuration.puppet_future do + it { pending "the puppet 4 implementation"; is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params('').and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params({}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params([]).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(true).and_raise_error(ArgumentError) } + it { is_expected.to run.with_params(1, 2, 'foo').and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, []).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, {}).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, true).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(ArgumentError) } + it { pending "the puppet 4 implementation"; is_expected.to run.with_params('1..2..3').and_raise_error(ArgumentError) } + end + + context 'with characters as bounds' do + it { is_expected.to run.with_params('d', 'a').and_return([]) } + it { is_expected.to run.with_params('a', 'a').and_return(['a']) } + it { is_expected.to run.with_params('a', 'b').and_return(['a', 'b']) } + it { is_expected.to run.with_params('a', 'd').and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', 1).and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', '1').and_return(['a', 'b', 'c', 'd']) } + it { is_expected.to run.with_params('a', 'd', 2).and_return(['a', 'c']) } + it { is_expected.to run.with_params('a', 'd', -2).and_return(['a', 'c']) } + it { is_expected.to run.with_params('a', 'd', 3).and_return(['a', 'd']) } + it { is_expected.to run.with_params('a', 'd', 4).and_return(['a']) } + end + + context 'with strings as bounds' do + it { is_expected.to run.with_params('onea', 'oned').and_return(['onea', 'oneb', 'onec', 'oned']) } + it { is_expected.to run.with_params('two', 'one').and_return([]) } + it { is_expected.to run.with_params('true', 'false').and_return([]) } + it { is_expected.to run.with_params('false', 'true').and_return(['false']) } + end + + context 'with integers as bounds' do + it { is_expected.to run.with_params(4, 1).and_return([]) } + it { is_expected.to run.with_params(1, 1).and_return([1]) } + it { is_expected.to run.with_params(1, 2).and_return([1, 2]) } + it { is_expected.to run.with_params(1, 4).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, 1).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, '1').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params(1, 4, 2).and_return([1, 3]) } + it { is_expected.to run.with_params(1, 4, -2).and_return([1, 3]) } + it { is_expected.to run.with_params(1, 4, 3).and_return([1, 4]) } + it { is_expected.to run.with_params(1, 4, 4).and_return([1]) } + end + + context 'with integers as strings as bounds' do + it { is_expected.to run.with_params('4', '1').and_return([]) } + it { is_expected.to run.with_params('1', '1').and_return([1]) } + it { is_expected.to run.with_params('1', '2').and_return([1, 2]) } + it { is_expected.to run.with_params('1', '4').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', 1).and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', '1').and_return([1, 2, 3, 4]) } + it { is_expected.to run.with_params('1', '4', 2).and_return([1, 3]) } + it { is_expected.to run.with_params('1', '4', -2).and_return([1, 3]) } + it { is_expected.to run.with_params('1', '4', 3).and_return([1, 4]) } + it { is_expected.to run.with_params('1', '4', 4).and_return([1]) } + end + + context 'with prefixed numbers as strings as bounds' do + it { is_expected.to run.with_params('host01', 'host04').and_return(['host01', 'host02', 'host03', 'host04']) } + it { is_expected.to run.with_params('01', '04').and_return([1, 2, 3, 4]) } + end + + context 'with prefixed numbers as utf8 strings as bounds' do + it { is_expected.to run.with_params('ħөŝŧ01', 'ħөŝŧ04').and_return(['ħөŝŧ01', 'ħөŝŧ02', 'ħөŝŧ03', 'ħөŝŧ04']) } + end + + context 'with prefixed numbers as double byte character strings as bounds' do + it { is_expected.to run.with_params('ホスト01', 'ホスト04').and_return(['ホスト01', 'ホスト02', 'ホスト03', 'ホスト04']) } + end + + context 'with dash-range syntax' do + it { is_expected.to run.with_params('4-1').and_return([]) } + it { is_expected.to run.with_params('1-1').and_return([1]) } + it { is_expected.to run.with_params('1-2').and_return([1, 2]) } + it { is_expected.to run.with_params('1-4').and_return([1, 2, 3, 4]) } + end + + context 'with two-dot-range syntax' do + it { is_expected.to run.with_params('4..1').and_return([]) } + it { is_expected.to run.with_params('1..1').and_return([1]) } + it { is_expected.to run.with_params('1..2').and_return([1, 2]) } + it { is_expected.to run.with_params('1..4').and_return([1, 2, 3, 4]) } + end + + context 'with three-dot-range syntax' do + it { is_expected.to run.with_params('4...1').and_return([]) } + it { is_expected.to run.with_params('1...1').and_return([]) } + it { is_expected.to run.with_params('1...2').and_return([1]) } + it { is_expected.to run.with_params('1...3').and_return([1, 2]) } + it { is_expected.to run.with_params('1...5').and_return([1, 2, 3, 4]) } + end + + describe 'when passing mixed arguments as bounds' do + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params('0', 'a').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params(0, 'a').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + it { + pending('these bounds should not be allowed as ruby will OOM hard. e.g. `(\'host0\'..\'hosta\').to_a` has 3239930 elements on ruby 1.9, adding more \'0\'s and \'a\'s increases that exponentially') + is_expected.to run.with_params('h0', 'ha').and_raise_error(Puppet::ParseError, /cannot interpolate between numeric and non-numeric bounds/) + } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/regexpescape_spec.rb b/environments/production/modules/stdlib/spec/functions/regexpescape_spec.rb new file mode 100644 index 0000000..36dbe70 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/regexpescape_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'regexpescape' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + describe 'handling normal strings' do + it 'should call ruby\'s Regexp.escape function' do + Regexp.expects(:escape).with('regexp_string').returns('escaped_regexp_string').once + is_expected.to run.with_params('regexp_string').and_return('escaped_regexp_string') + end + end + + describe 'handling classes derived from String' do + it 'should call ruby\'s Regexp.escape function' do + regexp_string = AlsoString.new('regexp_string') + Regexp.expects(:escape).with(regexp_string).returns('escaped_regexp_string').once + is_expected.to run.with_params(regexp_string).and_return("escaped_regexp_string") + end + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one*', "two"]).and_return(['one\*', "two"]) } + it { is_expected.to run.with_params(['one*', 1, true, {}, "two"]).and_return(['one\*', 1, true, {}, "two"]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['ŏʼnε*']).and_return(['ŏʼnε\*']) } + it { is_expected.to run.with_params(['インターネット*']).and_return(['インターネット\*']) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/reject_spec.rb b/environments/production/modules/stdlib/spec/functions/reject_spec.rb new file mode 100755 index 0000000..86db7c7 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/reject_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe 'reject' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'pattern', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + it { + pending("reject does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { + pending("reject does not actually check this, and raises NoMethodError instead") + is_expected.to run.with_params(1, 'two').and_raise_error(Puppet::ParseError, /first argument not an array/) + } + it { is_expected.to run.with_params([], 'two').and_return([]) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'two').and_return(['one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 't(wo|hree)').and_return(['one']) } + it { is_expected.to run.with_params(['όʼnệ', 'ţщồ', 'ţңяέέ'], 'ţ(щồ|ңяέέ)').and_return(['όʼnệ']) } +end diff --git a/environments/production/modules/stdlib/spec/functions/reverse_spec.rb b/environments/production/modules/stdlib/spec/functions/reverse_spec.rb new file mode 100755 index 0000000..79bc0ad --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/reverse_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'reverse' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(['three', 'two', 'one']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(['four', 'three', 'two', 'one']) } + it { is_expected.to run.with_params(['ổňë', 'ťŵọ', 'ŧңяəė', 'ƒŏůŗ']).and_return(['ƒŏůŗ', 'ŧңяəė', 'ťŵọ', 'ổňë']) } + + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('abc').and_return('cba') } + it { is_expected.to run.with_params('abcd').and_return('dcba') } + it { is_expected.to run.with_params('āβćđ').and_return('đćβā') } + + context 'when using a class extending String' do + it 'should call its reverse method' do + value = AlsoString.new('asdfghjkl') + value.expects(:reverse).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/rstrip_spec.rb b/environments/production/modules/stdlib/spec/functions/rstrip_spec.rb new file mode 100755 index 0000000..a7663e2 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/rstrip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'rstrip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return(' one') } + it { is_expected.to run.with_params(' one').and_return(' one') } + it { is_expected.to run.with_params("\tone").and_return("\tone") } + it { is_expected.to run.with_params("\t one").and_return("\t one") } + it { is_expected.to run.with_params('one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return(' one') } + it { is_expected.to run.with_params(' one ').and_return(' one') } + it { is_expected.to run.with_params(' ǿňè ').and_return(' ǿňè') } + it { is_expected.to run.with_params("\tone ").and_return("\tone") } + it { is_expected.to run.with_params("\t one ").and_return("\t one") } + it { is_expected.to run.with_params("one\t").and_return('one') } + it { is_expected.to run.with_params(" one\t").and_return(' one') } + it { is_expected.to run.with_params(" one\t").and_return(' one') } + it { is_expected.to run.with_params("\tone\t").and_return("\tone") } + it { is_expected.to run.with_params("\t one\t").and_return("\t one") } + it { is_expected.to run.with_params(' o n e ').and_return(' o n e') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return(' one') } +end diff --git a/environments/production/modules/stdlib/spec/functions/seeded_rand_spec.rb b/environments/production/modules/stdlib/spec/functions/seeded_rand_spec.rb new file mode 100644 index 0000000..ac108f4 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/seeded_rand_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe 'seeded_rand' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(ArgumentError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(0, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1.5, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(-10, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("-10", '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params("string", '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params([], '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params({}, '').and_raise_error(ArgumentError, /first argument must be a positive integer/) } + it { is_expected.to run.with_params(1, 1).and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params(1, []).and_raise_error(ArgumentError, /second argument must be a string/) } + it { is_expected.to run.with_params(1, {}).and_raise_error(ArgumentError, /second argument must be a string/) } + + it "provides a random number strictly less than the given max" do + expect(seeded_rand(3, 'seed')).to satisfy {|n| n.to_i < 3 } + end + + it "provides a random number greater or equal to zero" do + expect(seeded_rand(3, 'seed')).to satisfy {|n| n.to_i >= 0 } + end + + it "provides the same 'random' value on subsequent calls for the same host" do + expect(seeded_rand(10, 'seed')).to eql(seeded_rand(10, 'seed')) + end + + it "allows seed to control the random value on a single host" do + first_random = seeded_rand(1000, 'seed1') + second_different_random = seeded_rand(1000, 'seed2') + + expect(first_random).not_to eql(second_different_random) + end + + it "should not return different values for different hosts" do + val1 = seeded_rand(1000, 'foo', :host => "first.host.com") + val2 = seeded_rand(1000, 'foo', :host => "second.host.com") + + expect(val1).to eql(val2) + end + + def seeded_rand(max, seed, args = {}) + host = args[:host] || '127.0.0.1' + + # workaround not being able to use let(:facts) because some tests need + # multiple different hostnames in one context + scope.stubs(:lookupvar).with("::fqdn", {}).returns(host) + + scope.function_seeded_rand([max, seed]) + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(1000, 'ǿňè')} + it { is_expected.to run.with_params(1000, '文字列')} + end +end diff --git a/environments/production/modules/stdlib/spec/functions/shell_escape_spec.rb b/environments/production/modules/stdlib/spec/functions/shell_escape_spec.rb new file mode 100644 index 0000000..77917dd --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/shell_escape_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe 'shell_escape' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'stringification' do + it { is_expected.to run.with_params(10).and_return('10') } + it { is_expected.to run.with_params(false).and_return('false') } + end + + describe 'escaping' do + it { is_expected.to run.with_params('foo').and_return('foo') } + it { is_expected.to run.with_params('foo bar').and_return('foo\ bar') } + it { is_expected.to run.with_params('~`!@#$%^&*()_+-=[]\{}|;\':",./<>?') + .and_return('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?') } + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('スペー スを含むテ キスト').and_return('\\ス\\ペ\\ー\\ \\ス\\を\\含\\む\\テ\\ \\ \\キ\\ス\\ト') } + it { is_expected.to run.with_params('μťƒ 8 ŧĕχť').and_return('\\μ\\ť\\ƒ\\ 8\\ \\ \\ŧ\\ĕ\\χ\\ť') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/shell_join_spec.rb b/environments/production/modules/stdlib/spec/functions/shell_join_spec.rb new file mode 100644 index 0000000..46305bf --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/shell_join_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe 'shell_join' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(['foo'], ['bar']).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo').and_raise_error(Puppet::ParseError, /is not an Array/i) } + end + + describe 'shell argument joining' do + it { is_expected.to run.with_params(['foo']).and_return('foo') } + it { is_expected.to run.with_params(['foo', 'bar']).and_return('foo bar') } + it { is_expected.to run.with_params(['foo', 'bar baz']).and_return('foo bar\ baz') } + it { is_expected.to run.with_params(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?']) + .and_return('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['μťƒ', '8', 'ŧĕχť']).and_return('\\μ\\ť\\ƒ 8 \\ŧ\\ĕ\\χ\\ť') } + it { is_expected.to run.with_params(['スペー', 'スを含むテ', ' キスト']).and_return('\\ス\\ペ\\ー \\ス\\を\\含\\む\\テ \\ \\キ\\ス\\ト') } + end + end + + describe 'stringification' do + it { is_expected.to run.with_params([10, false, 'foo']).and_return('10 false foo') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/shell_split_spec.rb b/environments/production/modules/stdlib/spec/functions/shell_split_spec.rb new file mode 100644 index 0000000..f8f9c90 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/shell_split_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'shell_split' do + it { is_expected.not_to eq(nil) } + + describe 'signature validation' do + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'stringification' do + it { is_expected.to run.with_params(10).and_return(['10']) } + it { is_expected.to run.with_params(false).and_return(['false']) } + end + + describe 'shell line spliting' do + it { is_expected.to run.with_params('foo').and_return(['foo']) } + it { is_expected.to run.with_params('foo bar').and_return(['foo', 'bar']) } + it { is_expected.to run.with_params('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?') + .and_return(['~`!@#$%^&*()_+-=[]\{}|;\':",./<>?']) } + it { is_expected.to run.with_params('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?') + .and_return(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?']) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('\\μ\\ť\\ƒ 8 \\ŧ\\ĕ\\χ\\ť').and_return(['μťƒ', '8', 'ŧĕχť']) } + it { is_expected.to run.with_params('\\ス\\ペ\\ー \\ス\\を\\含\\む\\テ \\ \\キ\\ス\\ト').and_return(['スペー', 'スを含むテ', ' キスト']) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/shuffle_spec.rb b/environments/production/modules/stdlib/spec/functions/shuffle_spec.rb new file mode 100755 index 0000000..4673daa --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/shuffle_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'shuffle' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + + context 'when running with a specific seed' do + # make tests deterministic + before(:each) { srand(2) } + + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(['two', 'one', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(['four', 'three', 'two', 'one']) } + + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('abc').and_return('bac') } + it { is_expected.to run.with_params('abcd').and_return('dcba') } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('ůţƒ8 ŧέχŧ şŧґíńģ').and_return('ģńş ůχţέƒŧí8ґŧŧ ') } + it { is_expected.to run.with_params('日本語の文字列').and_return('字本日語文列の') } + end + + context 'when using a class extending String' do + it { is_expected.to run.with_params(AlsoString.new('asdfghjkl')).and_return('lkhdsfajg') } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/size_spec.rb b/environments/production/modules/stdlib/spec/functions/size_spec.rb new file mode 100755 index 0000000..2047423 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/size_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'size' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Unknown type given/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Unknown type given/) } + it { is_expected.to run.with_params('1').and_raise_error(Puppet::ParseError, /Requires either string, array or hash to work/) } + it { is_expected.to run.with_params('1.0').and_raise_error(Puppet::ParseError, /Requires either string, array or hash to work/) } + it { is_expected.to run.with_params([]).and_return(0) } + it { is_expected.to run.with_params(['a']).and_return(1) } + it { is_expected.to run.with_params(['one', 'two', 'three']).and_return(3) } + it { is_expected.to run.with_params(['one', 'two', 'three', 'four']).and_return(4) } + + it { is_expected.to run.with_params({}).and_return(0) } + it { is_expected.to run.with_params({'1' => '2'}).and_return(1) } + it { is_expected.to run.with_params({'1' => '2', '4' => '4'}).and_return(2) } + it { is_expected.to run.with_params({'€' => '@', '竹' => 'ǿňè'}).and_return(2) } + + it { is_expected.to run.with_params('').and_return(0) } + it { is_expected.to run.with_params('a').and_return(1) } + it { is_expected.to run.with_params('abc').and_return(3) } + it { is_expected.to run.with_params('abcd').and_return(4) } + it { is_expected.to run.with_params('万').and_return(1) } + it { is_expected.to run.with_params('āβćđ').and_return(4) } + + context 'when using a class extending String' do + it 'should call its size method' do + value = AlsoString.new('asdfghjkl') + value.expects(:size).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/sort_spec.rb b/environments/production/modules/stdlib/spec/functions/sort_spec.rb new file mode 100755 index 0000000..9abd039 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/sort_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'sort' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { pending('stricter input checking'); is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /requires string or array/) } + it { pending('stricter input checking'); is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /requires string or array/) } + it { pending('stricter input checking'); is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /requires string or array/) } + end + + context 'when called with an array' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['c', 'b', 'a']).and_return(['a', 'b', 'c']) } + end + + context 'when called with a string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('cbda').and_return('abcd') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/squeeze_spec.rb b/environments/production/modules/stdlib/spec/functions/squeeze_spec.rb new file mode 100755 index 0000000..b267d9a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/squeeze_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe 'squeeze' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(NoMethodError) } + it { is_expected.to run.with_params({}).and_raise_error(NoMethodError) } + it { is_expected.to run.with_params(true).and_raise_error(NoMethodError) } + + context 'when squeezing a single string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaa').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaa', 'a').and_return('a') } + it { is_expected.to run.with_params('aaaaaaaaabbbbbbbbbbcccccccccc', 'b-c').and_return('aaaaaaaaabc') } + end + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params('ậậậậậậậậậậậậậậậậậậậậ').and_return('ậ') } + it { is_expected.to run.with_params('語語語語語語語', '語').and_return('語') } + it { is_expected.to run.with_params('ậậậậậậậậậậậậậậậậậ語語語語©©©©©', '©').and_return('ậậậậậậậậậậậậậậậậậ語語語語©') } + end + + context 'when squeezing values in an array' do + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc']) \ + .and_return( ['', 'a', 'a', 'abc']) + } + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc'], 'a') \ + .and_return( ['', 'a', 'a', 'abbbbbbbbbbcccccccccc']) + } + it { + is_expected.to run \ + .with_params(['', 'a', 'aaaaaaaaa', 'aaaaaaaaabbbbbbbbbbcccccccccc'], 'b-c') \ + .and_return( ['', 'a', 'aaaaaaaaa', 'aaaaaaaaabc']) + } + end + + context 'when using a class extending String' do + it 'should call its squeeze method' do + value = AlsoString.new('aaaaaaaaa') + value.expects(:squeeze).returns('foo') + expect(subject).to run.with_params(value).and_return('foo') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/str2bool_spec.rb b/environments/production/modules/stdlib/spec/functions/str2bool_spec.rb new file mode 100755 index 0000000..7d8c47c --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/str2bool_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'str2bool' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('true', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /Unknown type of boolean given/) } + + describe 'when testing values that mean "true"' do + [ 'TRUE','1', 't', 'y', 'true', 'yes', true ].each do |value| + it { is_expected.to run.with_params(value).and_return(true) } + end + end + + describe 'when testing values that mean "false"' do + [ 'FALSE','', '0', 'f', 'n', 'false', 'no', false, 'undef', 'undefined' ].each do |value| + it { is_expected.to run.with_params(value).and_return(false) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/str2saltedsha512_spec.rb b/environments/production/modules/stdlib/spec/functions/str2saltedsha512_spec.rb new file mode 100755 index 0000000..2e1e818 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/str2saltedsha512_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'str2saltedsha512' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('password', 2).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires a String argument/) } + + context 'when running with a specific seed' do + # make tests deterministic + before(:each) { srand(2) } + + it { is_expected.to run.with_params('').and_return('0f8a612f4eeed08e47b3875d00f33c5688f7926298f2d9b5fe19d1323f910bc78b6f7b5892596d2fabaa65e7a8d99b3768c102610cf0432c4827eee01f09451e3fae4f7a') } + it { is_expected.to run.with_params('password').and_return('0f8a612f43134376566c5707718d600effcfb17581fc9d3fa64d7f447dfda317c174ffdb498d2c5bd5c2075dab41c9d7ada5afbdc6b55354980eb5ba61802371e6b64956') } + it { is_expected.to run.with_params('verylongpassword').and_return('0f8a612f7a448537540e062daa8621f9bae326ca8ccb899e1bdb10e7c218cebfceae2530b856662565fdc4d81e986fc50cfbbc46d50436610ed9429ff5e43f2c45b5d039') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/strftime_spec.rb b/environments/production/modules/stdlib/spec/functions/strftime_spec.rb new file mode 100755 index 0000000..e76774a --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/strftime_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'strftime' do + it "should exist" do + expect(Puppet::Parser::Functions.function("strftime")).to eq("function_strftime") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_strftime([]) }.to( raise_error(Puppet::ParseError)) + end + + it "using %s should be higher then when I wrote this test" do + result = scope.function_strftime(["%s"]) + expect(result.to_i).to(be > 1311953157) + end + + it "using %s should be lower then 1.5 trillion" do + result = scope.function_strftime(["%s"]) + expect(result.to_i).to(be < 1500000000) + end + + it "should return a date when given %Y-%m-%d" do + result = scope.function_strftime(["%Y-%m-%d"]) + expect(result).to match(/^\d{4}-\d{2}-\d{2}$/) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/strip_spec.rb b/environments/production/modules/stdlib/spec/functions/strip_spec.rb new file mode 100755 index 0000000..18e943d --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/strip_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'strip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work with/) } + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params(' ').and_return('') } + it { is_expected.to run.with_params("\t").and_return('') } + it { is_expected.to run.with_params("\t ").and_return('') } + it { is_expected.to run.with_params('one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params(' one').and_return('one') } + it { is_expected.to run.with_params("\tone").and_return('one') } + it { is_expected.to run.with_params("\t one").and_return('one') } + it { is_expected.to run.with_params('one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return('one') } + it { is_expected.to run.with_params(' one ').and_return('one') } + it { is_expected.to run.with_params("\tone ").and_return('one') } + it { is_expected.to run.with_params("\t one ").and_return('one') } + it { is_expected.to run.with_params("one \t").and_return('one') } + it { is_expected.to run.with_params(" one \t").and_return('one') } + it { is_expected.to run.with_params(" one \t").and_return('one') } + it { is_expected.to run.with_params("\tone \t").and_return('one') } + it { is_expected.to run.with_params("\t one \t").and_return('one') } + it { is_expected.to run.with_params(' o n e ').and_return('o n e') } + it { is_expected.to run.with_params(' ỏŋέ ').and_return('ỏŋέ') } + it { is_expected.to run.with_params(AlsoString.new(' one ')).and_return('one') } +end diff --git a/environments/production/modules/stdlib/spec/functions/suffix_spec.rb b/environments/production/modules/stdlib/spec/functions/suffix_spec.rb new file mode 100755 index 0000000..e0eafb1 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/suffix_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'suffix' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the second.") + is_expected.to run.with_params([], 'a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /expected first argument to be an Array/) } + it { is_expected.to run.with_params([], 2).and_raise_error(Puppet::ParseError, /expected second argument to be a String/) } + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['one', 2]).and_return(['one', '2']) } + it { is_expected.to run.with_params([], '').and_return([]) } + it { is_expected.to run.with_params([''], '').and_return(['']) } + it { is_expected.to run.with_params(['one'], 'post').and_return(['onepost']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], 'post').and_return(['onepost', 'twopost', 'threepost']) } + it { is_expected.to run.with_params(['ỗńέ', 'ťשׂǿ', 'ŧҺř℮ə'], 'рổŝţ').and_return(['ỗńέрổŝţ', 'ťשׂǿрổŝţ', 'ŧҺř℮əрổŝţ']) } + + it { + is_expected.to run.with_params({}).and_return({}) + } + it { + is_expected.to run.with_params({ 'key1' => 'value1', 2 => 3}).and_return({ 'key1' => 'value1', '2' => 3 }) + } + it { + is_expected.to run.with_params({}, '').and_return({}) + } + it { + is_expected.to run.with_params({ 'key' => 'value' }, '').and_return({ 'key' => 'value' }) + } + it { + is_expected.to run.with_params({ 'key' => 'value' }, 'post').and_return({ 'keypost' => 'value' }) + } + it { + is_expected.to run \ + .with_params({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }, 'post') \ + .and_return({ 'key1post' => 'value1', 'key2post' => 'value2', 'key3post' => 'value3' }) + } +end diff --git a/environments/production/modules/stdlib/spec/functions/swapcase_spec.rb b/environments/production/modules/stdlib/spec/functions/swapcase_spec.rb new file mode 100755 index 0000000..c175a15 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/swapcase_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'swapcase' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + describe 'with strings as inputs' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('one').and_return('ONE') } + it { is_expected.to run.with_params('ONE').and_return('one') } + it { is_expected.to run.with_params('oNe').and_return('OnE') } + end + describe 'with arrays as inputs' do + it { is_expected.to run.with_params([]).and_return([]) } + describe 'only containing strings' do + it { is_expected.to run.with_params(['']).and_return(['']) } + it { is_expected.to run.with_params(['one']).and_return(['ONE']) } + it { is_expected.to run.with_params(['ONE']).and_return(['one']) } + it { is_expected.to run.with_params(['oNe']).and_return(['OnE']) } + it { is_expected.to run.with_params(['one', 'ONE']).and_return(['ONE', 'one']) } + it { is_expected.to run.with_params(['ONE', 'OnE']).and_return(['one', 'oNe']) } + it { is_expected.to run.with_params(['oNe', 'one']).and_return(['OnE', 'ONE']) } + end + describe 'containing mixed types' do + it { is_expected.to run.with_params(['OnE', {}]).and_return(['oNe', {}]) } + it { is_expected.to run.with_params(['OnE', 1]).and_return(['oNe', 1]) } + it { is_expected.to run.with_params(['OnE', []]).and_return(['oNe', []]) } + it { is_expected.to run.with_params(['OnE', ['two']]).and_return(['oNe', ['two']]) } + end + end + it "should accept objects which extend String" do + is_expected.to run.with_params(AlsoString.new("OnE")).and_return('oNe') + end +end diff --git a/environments/production/modules/stdlib/spec/functions/time_spec.rb b/environments/production/modules/stdlib/spec/functions/time_spec.rb new file mode 100755 index 0000000..d157939 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/time_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'time' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params('a', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + context 'when running at a specific time' do + before(:each) { + # get a value before stubbing the function + test_time = Time.utc(2006, 10, 13, 8, 15, 11) + Time.expects(:new).with().returns(test_time).once + } + it { is_expected.to run.with_params().and_return(1160727311) } + it { is_expected.to run.with_params('').and_return(1160727311) } + it { is_expected.to run.with_params([]).and_return(1160727311) } + it { is_expected.to run.with_params({}).and_return(1160727311) } + it { is_expected.to run.with_params('foo').and_return(1160727311) } + it { is_expected.to run.with_params('UTC').and_return(1160727311) } + it { is_expected.to run.with_params('America/New_York').and_return(1160727311) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/to_bytes_spec.rb b/environments/production/modules/stdlib/spec/functions/to_bytes_spec.rb new file mode 100755 index 0000000..2be23ff --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/to_bytes_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'to_bytes' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('1', 'extras').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(TypeError, /(can't convert|no implicit conversion of) Array (in)?to String/) } + it { is_expected.to run.with_params({}).and_raise_error(TypeError, /(can't convert|no implicit conversion of) Hash (in)?to String/) } + it { is_expected.to run.with_params(true).and_raise_error(TypeError, /(can't convert|no implicit conversion of) (TrueClass|true) (in)?to String/) } + + describe 'when passing numbers' do + it { is_expected.to run.with_params(0).and_return(0) } + it { is_expected.to run.with_params(1).and_return(1) } + it { is_expected.to run.with_params(-1).and_return(-1) } + it { is_expected.to run.with_params(1.1).and_return(1.1) } + it { is_expected.to run.with_params(-1.1).and_return(-1.1) } + end + + describe 'when passing numbers as strings' do + describe 'without a unit' do + it { is_expected.to run.with_params('1').and_return(1) } + it { is_expected.to run.with_params('-1').and_return(-1) } + # these are so wrong + it { is_expected.to run.with_params('1.1').and_return(1) } + it { is_expected.to run.with_params('-1.1').and_return(-1) } + end + + describe 'with a unit' do + it { is_expected.to run.with_params('1k').and_return(1024) } + it { is_expected.to run.with_params('-1kB').and_return(-1024) } + it { is_expected.to run.with_params('1k').and_return(1024) } + it { is_expected.to run.with_params('1M').and_return(1024*1024) } + it { is_expected.to run.with_params('1G').and_return(1024*1024*1024) } + it { is_expected.to run.with_params('1T').and_return(1024*1024*1024*1024) } + it { is_expected.to run.with_params('1P').and_return(1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('1E').and_return(1024*1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('1.5e3M').and_return(1572864000) } + + it { is_expected.to run.with_params('4k').and_return(4*1024) } + it { is_expected.to run.with_params('-4kB').and_return(4*-1024) } + it { is_expected.to run.with_params('4k').and_return(4*1024) } + it { is_expected.to run.with_params('4M').and_return(4*1024*1024) } + it { is_expected.to run.with_params('4G').and_return(4*1024*1024*1024) } + it { is_expected.to run.with_params('4T').and_return(4*1024*1024*1024*1024) } + it { is_expected.to run.with_params('4P').and_return(4*1024*1024*1024*1024*1024) } + it { is_expected.to run.with_params('4E').and_return(4*1024*1024*1024*1024*1024*1024) } + + # these are so wrong + it { is_expected.to run.with_params('1.0001 k').and_return(1024) } + it { is_expected.to run.with_params('-1.0001 kB').and_return(-1024) } + end + + describe 'with a unknown unit' do + it { is_expected.to run.with_params('1KB').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1K').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1mb').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1m').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1%').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + it { is_expected.to run.with_params('1 p').and_raise_error(Puppet::ParseError, /Unknown prefix/) } + end + end + + # these are so wrong + describe 'when passing random stuff' do + it { is_expected.to run.with_params('-1....1').and_return(-1) } + it { is_expected.to run.with_params('-1.e.e.e.1').and_return(-1) } + it { is_expected.to run.with_params('-1+1').and_return(-1) } + it { is_expected.to run.with_params('1-1').and_return(1) } + it { is_expected.to run.with_params('1 kaboom').and_return(1024) } + it { is_expected.to run.with_params('kaboom').and_return(0) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/try_get_value_spec.rb b/environments/production/modules/stdlib/spec/functions/try_get_value_spec.rb new file mode 100644 index 0000000..38c0efd --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/try_get_value_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe 'try_get_value' do + + let(:data) do + { + 'a' => { + 'g' => '2', + 'e' => [ + 'f0', + 'f1', + { + 'x' => { + 'y' => 'z' + } + }, + 'f3', + ] + }, + 'b' => true, + 'c' => false, + 'd' => '1', + } + end + + context 'single values' do + it 'should exist' do + is_expected.not_to eq(nil) + end + + it 'should be able to return a single value' do + is_expected.to run.with_params('test').and_return('test') + end + + it 'should use the default value if data is a single value and path is present' do + is_expected.to run.with_params('test', 'path', 'default').and_return('default') + end + + it 'should return default if there is no data' do + is_expected.to run.with_params(nil, nil, 'default').and_return('default') + end + + it 'should be able to use data structures as default values' do + is_expected.to run.with_params('test', 'path', data).and_return(data) + end + end + + context 'structure values' do + it 'should be able to extracts a single hash value' do + is_expected.to run.with_params(data, 'd', 'default').and_return('1') + end + + it 'should be able to extract a deeply nested hash value' do + is_expected.to run.with_params(data, 'a/g', 'default').and_return('2') + end + + it 'should return the default value if the path is not found' do + is_expected.to run.with_params(data, 'missing', 'default').and_return('default') + end + + it 'should return the default value if the path is too long' do + is_expected.to run.with_params(data, 'a/g/c/d', 'default').and_return('default') + end + + it 'should support an array index in the path' do + is_expected.to run.with_params(data, 'a/e/1', 'default').and_return('f1') + end + + it 'should return the default value if an array index is not a number' do + is_expected.to run.with_params(data, 'a/b/c', 'default').and_return('default') + end + + it 'should return the default value if and index is out of array length' do + is_expected.to run.with_params(data, 'a/e/5', 'default').and_return('default') + end + + it 'should be able to path though both arrays and hashes' do + is_expected.to run.with_params(data, 'a/e/2/x/y', 'default').and_return('z') + end + + it 'should be able to return "true" value' do + is_expected.to run.with_params(data, 'b', 'default').and_return(true) + is_expected.to run.with_params(data, 'm', true).and_return(true) + end + + it 'should be able to return "false" value' do + is_expected.to run.with_params(data, 'c', 'default').and_return(false) + is_expected.to run.with_params(data, 'm', false).and_return(false) + end + + it 'should return "nil" if value is not found and no default value is provided' do + is_expected.to run.with_params(data, 'a/1').and_return(nil) + end + + it 'should be able to use a custom path separator' do + is_expected.to run.with_params(data, 'a::g', 'default', '::').and_return('2') + is_expected.to run.with_params(data, 'a::c', 'default', '::').and_return('default') + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/type3x_spec.rb b/environments/production/modules/stdlib/spec/functions/type3x_spec.rb new file mode 100644 index 0000000..c3eb1de --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/type3x_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'type3x' do + it "should exist" do + expect(Puppet::Parser::Functions.function("type3x")).to eq("function_type3x") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_type3x([]) }.to( raise_error(Puppet::ParseError)) + end + + it "should return string when given a string" do + result = scope.function_type3x(["aaabbbbcccc"]) + expect(result).to(eq('string')) + end + + it "should return array when given an array" do + result = scope.function_type3x([["aaabbbbcccc","asdf"]]) + expect(result).to(eq('array')) + end + + it "should return hash when given a hash" do + result = scope.function_type3x([{"a"=>1,"b"=>2}]) + expect(result).to(eq('hash')) + end + + it "should return integer when given an integer" do + result = scope.function_type3x(["1"]) + expect(result).to(eq('integer')) + end + + it "should return float when given a float" do + result = scope.function_type3x(["1.34"]) + expect(result).to(eq('float')) + end + + it "should return boolean when given a boolean" do + result = scope.function_type3x([true]) + expect(result).to(eq('boolean')) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/type_of_spec.rb b/environments/production/modules/stdlib/spec/functions/type_of_spec.rb new file mode 100644 index 0000000..cc9ef78 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/type_of_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +if ENV["FUTURE_PARSER"] == 'yes' + describe 'type_of' do + pending 'teach rspec-puppet to load future-only functions under 3.7.5' do + it { is_expected.not_to eq(nil) } + end + end +end + +if Puppet.version.to_f >= 4.0 + describe 'type_of' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('', '').and_raise_error(ArgumentError) } + + it 'gives the type of a string' do + expect(subject.call({}, 'hello world')).to be_kind_of(Puppet::Pops::Types::PStringType) + end + + it 'gives the type of an integer' do + expect(subject.call({}, 5)).to be_kind_of(Puppet::Pops::Types::PIntegerType) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/type_spec.rb b/environments/production/modules/stdlib/spec/functions/type_spec.rb new file mode 100755 index 0000000..4288df0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/type_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe 'type' do + it "should exist" do + expect(Puppet::Parser::Functions.function("type")).to eq("function_type") + end + + it "should give a deprecation warning when called" do + scope.expects(:warning).with("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.") + scope.function_type(["aoeu"]) + end + + it "should return string when given a string" do + result = scope.function_type(["aaabbbbcccc"]) + expect(result).to(eq('string')) + end + + it "should return array when given an array" do + result = scope.function_type([["aaabbbbcccc","asdf"]]) + expect(result).to(eq('array')) + end + + it "should return hash when given a hash" do + result = scope.function_type([{"a"=>1,"b"=>2}]) + expect(result).to(eq('hash')) + end + + it "should return integer when given an integer" do + result = scope.function_type(["1"]) + expect(result).to(eq('integer')) + end + + it "should return float when given a float" do + result = scope.function_type(["1.34"]) + expect(result).to(eq('float')) + end + + it "should return boolean when given a boolean" do + result = scope.function_type([true]) + expect(result).to(eq('boolean')) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/union_spec.rb b/environments/production/modules/stdlib/spec/functions/union_spec.rb new file mode 100755 index 0000000..3f36f24 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/union_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe 'union' do + describe 'argument checking' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', []).and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + it { is_expected.to run.with_params([], 'two').and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /Every parameter must be an array/) } + end + + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one'], []).and_return(['one']) } + it { is_expected.to run.with_params(['one'], ['one']).and_return(['one']) } + it { is_expected.to run.with_params(['one'], ['two']).and_return(['one', 'two']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'two', 'three'], ['two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two', 'three'], ['two', 'two', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two'], ['two', 'three'], ['one', 'three']).and_return(['one', 'two', 'three']) } + it { is_expected.to run.with_params(['one', 'two'], ['three', 'four'], ['one', 'two', 'three'], ['four']).and_return(['one', 'two', 'three', 'four']) } + it { is_expected.to run.with_params(['ốńə', 'ţשׂợ'], ['ŧĥяếệ', 'ƒởųŗ'], ['ốńə', 'ţשׂợ', 'ŧĥяếệ'], ['ƒởųŗ']).and_return(['ốńə', 'ţשׂợ', 'ŧĥяếệ', 'ƒởųŗ']) } + it 'should not confuse types' do is_expected.to run.with_params(['1', '2', '3'], [1, 2]).and_return(['1', '2', '3', 1, 2]) end +end diff --git a/environments/production/modules/stdlib/spec/functions/unique_spec.rb b/environments/production/modules/stdlib/spec/functions/unique_spec.rb new file mode 100755 index 0000000..7955acb --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/unique_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'unique' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params([], 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + context 'when called with an array' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(['a']).and_return(['a']) } + it { is_expected.to run.with_params(['a', 'b', 'a']).and_return(['a', 'b']) } + it { is_expected.to run.with_params(['ã', 'ъ', 'ã']).and_return(['ã', 'ъ']) } + end + + context 'when called with a string' do + it { is_expected.to run.with_params('').and_return('') } + it { is_expected.to run.with_params('a').and_return('a') } + it { is_expected.to run.with_params('aaba').and_return('ab') } + it { is_expected.to run.with_params('ããъã').and_return('ãъ') } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/unix2dos_spec.rb b/environments/production/modules/stdlib/spec/functions/unix2dos_spec.rb new file mode 100644 index 0000000..8537a26 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/unix2dos_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'unix2dos' do + context 'Checking parameter validity' do + it { is_expected.not_to eq(nil) } + it do + is_expected.to run.with_params.and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params('one', 'two').and_raise_error(ArgumentError, /Wrong number of arguments/) + end + it do + is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError) + end + it do + is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError) + end + end + + context 'Converting from unix to dos format' do + sample_text = "Hello\nWorld\n" + desired_output = "Hello\r\nWorld\r\n" + + it 'should output dos format' do + should run.with_params(sample_text).and_return(desired_output) + end + end + + context 'Converting from dos to dos format' do + sample_text = "Hello\r\nWorld\r\n" + desired_output = "Hello\r\nWorld\r\n" + + it 'should output dos format' do + should run.with_params(sample_text).and_return(desired_output) + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/upcase_spec.rb b/environments/production/modules/stdlib/spec/functions/upcase_spec.rb new file mode 100755 index 0000000..3b7b02d --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/upcase_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'upcase' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires an array, hash or object that responds to upcase/) } + it { is_expected.to run.with_params([1]).and_raise_error(Puppet::ParseError, /Requires an array, hash or object that responds to upcase/) } + end + + describe 'normal string handling' do + it { is_expected.to run.with_params("abc").and_return("ABC") } + it { is_expected.to run.with_params("Abc").and_return("ABC") } + it { is_expected.to run.with_params("ABC").and_return("ABC") } + end + + describe 'handling classes derived from String' do + it { is_expected.to run.with_params(AlsoString.new("ABC")).and_return("ABC") } + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["One", "twO"]).and_return(["ONE", "TWO"]) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/uriescape_spec.rb b/environments/production/modules/stdlib/spec/functions/uriescape_spec.rb new file mode 100755 index 0000000..f05ec08 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/uriescape_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'uriescape' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the first.") + is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /Requires either array or string to work/) } + end + + describe 'handling normal strings' do + it 'should call ruby\'s URI.escape function' do + URI.expects(:escape).with('uri_string').returns('escaped_uri_string').once + is_expected.to run.with_params('uri_string').and_return('escaped_uri_string') + end + end + + describe 'handling classes derived from String' do + it 'should call ruby\'s URI.escape function' do + uri_string = AlsoString.new('uri_string') + URI.expects(:escape).with(uri_string).returns('escaped_uri_string').once + is_expected.to run.with_params(uri_string).and_return("escaped_uri_string") + end + end + + describe 'strings in arrays handling' do + it { is_expected.to run.with_params([]).and_return([]) } + it { is_expected.to run.with_params(["one}", "two"]).and_return(["one%7D", "two"]) } + it { is_expected.to run.with_params(["one}", 1, true, {}, "two"]).and_return(["one%7D", 1, true, {}, "two"]) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_absolute_path_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_absolute_path_spec.rb new file mode 100755 index 0000000..9397da5 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_absolute_path_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe 'validate_absolute_path' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('c:/') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe "valid paths handling" do + %w{ + C:/ + C:\\ + C:\\WINDOWS\\System32 + C:/windows/system32 + X:/foo/bar + X:\\foo\\bar + \\\\host\\windows + //host/windows + / + /var/tmp + /var/opt/../lib/puppet + }.each do |path| + it { is_expected.to run.with_params(path) } + it { is_expected.to run.with_params(['/tmp', path]) } + end + end + + describe 'invalid path handling' do + context 'garbage inputs' do + [ + nil, + [ nil ], + [ nil, nil ], + { 'foo' => 'bar' }, + { }, + '', + ].each do |path| + it { is_expected.to run.with_params(path).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params([path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params(['/tmp', path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + end + end + + context 'relative paths' do + %w{ + relative1 + . + .. + ./foo + ../foo + etc/puppetlabs/puppet + opt/puppet/bin + relative\\windows + }.each do |path| + it { is_expected.to run.with_params(path).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params([path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + it { is_expected.to run.with_params(['/tmp', path]).and_raise_error(Puppet::ParseError, /is not an absolute path/) } + end + end + end +end + diff --git a/environments/production/modules/stdlib/spec/functions/validate_array_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_array_spec.rb new file mode 100755 index 0000000..409b3dc --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_array_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'validate_array' do + + describe 'signature validation' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + it { is_expected.not_to eq(nil) } + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params([]) + end + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params([]) } + it { is_expected.to run.with_params(['one']) } + it { is_expected.to run.with_params([], ['two']) } + it { is_expected.to run.with_params(['one'], ['two']) } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], {}).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], 1).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], true).and_raise_error(Puppet::ParseError, /is not an Array/) } + it { is_expected.to run.with_params([], 'one').and_raise_error(Puppet::ParseError, /is not an Array/) } + end + end +end + diff --git a/environments/production/modules/stdlib/spec/functions/validate_augeas_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_augeas_spec.rb new file mode 100755 index 0000000..4236649 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_augeas_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'validate_augeas' do + unless Puppet.features.augeas? + skip "ruby-augeas not installed" + else + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', [], '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('one', 'one', 'MSG to User', '4th arg').and_raise_error(NoMethodError) } + end + + describe 'valid inputs' do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns' ], + [ "proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns'], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input) } + end + end + + describe 'valid inputs which fail augeas validation' do + # The intent here is to make sure valid inputs raise exceptions when they + # don't specify an error message to display. This is the behvior in + # 2.2.x and prior. + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns' ], + [ "127.0.1.1\n", 'Hosts.lns' ], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /validate_augeas.*?matched less than it should/) } + end + end + + describe "when specifying nice error messages" do + # The intent here is to make sure the function returns the 4th argument + # in the exception thrown + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns', [], 'Failed to validate passwd content' ], + [ "127.0.1.1\n", 'Hosts.lns', [], 'Wrong hosts content' ], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /#{input[3]}/) } + end + end + + describe "matching additional tests" do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input) } + end + end + + describe "failing additional tests" do + inputs = [ + [ "foobar:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/sh\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it { is_expected.to run.with_params(*input).and_raise_error(Puppet::ParseError, /testing path/) } + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_bool_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_bool_spec.rb new file mode 100755 index 0000000..3074d88 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_bool_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'validate_bool' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(true) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'acceptable values' do + it { is_expected.to run.with_params(true) } + it { is_expected.to run.with_params(false) } + it { is_expected.to run.with_params(true, false, false, true) } + end + + describe 'validation failures' do + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params(true, 'one').and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params('one', false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("false").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params(true, "false").and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true", false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + it { is_expected.to run.with_params("true", false, false, false, false, false).and_raise_error(Puppet::ParseError, /is not a boolean/) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_cmd_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_cmd_spec.rb new file mode 100755 index 0000000..c6559e8 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_cmd_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'validate_cmd', :unless => Puppet::Util::Platform.windows? do + let(:touch) { File.exists?('/usr/bin/touch') ? '/usr/bin/touch' : '/bin/touch' } + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params([], '', '').and_raise_error(Puppet::ParseError, /content must be a string/) + } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params('', [], '').and_raise_error(Puppet::ParseError, /checkscript must be a string/) + } + it { + pending('should implement stricter type checking') + is_expected.to run.with_params('', '', []).and_raise_error(Puppet::ParseError, /custom error message must be a string/) + } + end + + context 'when validation fails' do + context 'with % placeholder' do + it { is_expected.to run.with_params('', "#{touch} % /no/such/file").and_raise_error(Puppet::ParseError, /Execution of '#{touch} \S+ \/no\/such\/file' returned 1:.*(cannot touch|o such file or)/) } + it { is_expected.to run.with_params('', "#{touch} % /no/such/file", 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + end + context 'without % placeholder' do + it { is_expected.to run.with_params('', "#{touch} /no/such/file").and_raise_error(Puppet::ParseError, /Execution of '#{touch} \/no\/such\/file \S+' returned 1:.*(cannot touch|o such file or)/) } + it { is_expected.to run.with_params('', "#{touch} /no/such/file", 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_email_address_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_email_address_spec.rb new file mode 100644 index 0000000..7628383 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_email_address_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'validate_email_address' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('bob@gmail.com') } + it { is_expected.to run.with_params('alice+puppetlabs.com@gmail.com') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid email/) } + it { is_expected.to run.with_params('bob@gmail.com', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('bob@gmail.com', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('bob@gmail.com', 'one').and_raise_error(Puppet::ParseError, /is not a valid email/) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_hash_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_hash_spec.rb new file mode 100755 index 0000000..7533abe --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_hash_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'validate_hash' do + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'check for deprecation warning' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params({'key' => 'value'}) + end + end + + describe 'valid inputs' do + it { is_expected.to run.with_params({}) } + it { is_expected.to run.with_params({'key' => 'value'}) } + it { is_expected.to run.with_params({}, {'key' => 'value'}) } + it { is_expected.to run.with_params({'key1' => 'value1'}, {'key2' => 'value2'}) } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, []).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, 1).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, true).and_raise_error(Puppet::ParseError, /is not a Hash/) } + it { is_expected.to run.with_params({}, 'one').and_raise_error(Puppet::ParseError, /is not a Hash/) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_integer_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_integer_spec.rb new file mode 100755 index 0000000..6558d00 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_integer_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe 'validate_integer' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(3) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', 7.0, -7.0, {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x'].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params(invalid, 10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params(invalid, 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + it { is_expected.to run.with_params([0, 1, 2, invalid, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be an Integer/) } + end + + context 'when running on ruby, which munges hashes weirdly', :if => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([0, 1, 2, {0=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params(1, '').and_raise_error(Puppet::ParseError, /to be unset or an Integer/) } + it { is_expected.to run.with_params(1, 2, '').and_raise_error(Puppet::ParseError, /to be unset or an Integer/) } + it { is_expected.to run.with_params(1, 2, 3).and_raise_error(Puppet::ParseError, /second argument to be larger than third argument/) } + end + + context 'with no range constraints' do + it { is_expected.to run.with_params(1) } + it { is_expected.to run.with_params(-1) } + it { is_expected.to run.with_params('1') } + it { is_expected.to run.with_params('-1') } + it { is_expected.to run.with_params([1, 2, 3, 4]) } + it { is_expected.to run.with_params([1, '2', '3', 4]) } + end + + context "with a maximum limit of 10" do + describe 'rejects numbers greater than the limit' do + it { is_expected.to run.with_params(11, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10,100], 10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'accepts numbers less or equal to the limit' do + it { is_expected.to run.with_params(10, 10) } + it { is_expected.to run.with_params(1, 10) } + it { is_expected.to run.with_params(-1, 10) } + it { is_expected.to run.with_params('1', 10) } + it { is_expected.to run.with_params('-1', 10) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10) } + end + + context "with a minimum limit of -10" do + describe 'rejects numbers greater than the upper limit' do + it { is_expected.to run.with_params(11, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10,100], 10, -10).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'rejects numbers smaller than the lower limit' do + it { is_expected.to run.with_params(-11, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-100, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-2**65, 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params([-10, 1,2,10,-100], 10, -10).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + end + + describe 'accepts numbers between and including the limits' do + it { is_expected.to run.with_params(10, 10, -10) } + it { is_expected.to run.with_params(-10, 10, -10) } + it { is_expected.to run.with_params(1, 10, -10) } + it { is_expected.to run.with_params(-1, 10, -10) } + it { is_expected.to run.with_params('1', 10, -10) } + it { is_expected.to run.with_params('-1', 10, -10) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10, -10) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10, -10) } + end + end + end + + it { is_expected.to run.with_params(10, 10, 10) } + + describe 'empty upper limit is interpreted as infinity' do + it { is_expected.to run.with_params(11, '', 10) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_ip_address_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_ip_address_spec.rb new file mode 100644 index 0000000..0414f5e --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_ip_address_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe 'validate_ip_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('0.0.0.0') } + it { is_expected.to run.with_params('8.8.8.8') } + it { is_expected.to run.with_params('127.0.0.1') } + it { is_expected.to run.with_params('10.10.10.10') } + it { is_expected.to run.with_params('194.232.104.150') } + it { is_expected.to run.with_params('244.24.24.24') } + it { is_expected.to run.with_params('255.255.255.255') } + it { is_expected.to run.with_params('1.2.3.4', '5.6.7.8') } + it { is_expected.to run.with_params('3ffe:0505:0002::') } + it { is_expected.to run.with_params('3ffe:0505:0002::', '3ffe:0505:0002::2') } + it { is_expected.to run.with_params('::1/64') } + it { is_expected.to run.with_params('fe80::a00:27ff:fe94:44d6/64') } + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1.2.3.4') + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('1.2.3.4') + end + end + + context 'with netmasks' do + it { is_expected.to run.with_params('8.8.8.8/0') } + it { is_expected.to run.with_params('8.8.8.8/16') } + it { is_expected.to run.with_params('8.8.8.8/32') } + it { is_expected.to run.with_params('8.8.8.8/255.255.0.0') } + end + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0.256').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('0.0.0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('1.2.3.4', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', 1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('1.2.3.4', 'one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + it { is_expected.to run.with_params('::1', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 'one').and_raise_error(Puppet::ParseError, /is not a valid IP/) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb new file mode 100755 index 0000000..6e4ca05 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'validate_ipv4_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first) + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first) + end + end + + SharedData::IPV4_PATTERNS.each do |value| + it { is_expected.to run.with_params(value) } + end + + SharedData::IPV4_NEGATIVE_PATTERNS.each do |value| + it { is_expected.to run.with_params(value).and_raise_error(Puppet::ParseError, /is not a valid IPv4/) } + end + + describe 'invalid inputs' do + [ {}, [], 1, true ].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(SharedData::IPV4_PATTERNS.first, invalid).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb new file mode 100755 index 0000000..137db36 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe 'validate_ipv6_address' do + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + end + + context 'Checking for deprecation warning', if: Puppet.version.to_f < 4.0 do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + # Checking for deprecation warning, which should only be provoked when the env variable for it is set. + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('3ffe:0505:0002::') + end + it 'should display no warning for deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "false" + scope.expects(:warning).with(includes('This method is deprecated')).never + is_expected.to run.with_params('3ffe:0505:0002::') + end + end + + describe 'valid inputs' do + it { is_expected.to run.with_params('3ffe:0505:0002::') } + it { is_expected.to run.with_params('3ffe:0505:0002::', '3ffe:0505:0002::2') } + it { is_expected.to run.with_params('::1/64') } + it { is_expected.to run.with_params('fe80::a00:27ff:fe94:44d6/64') } + it { is_expected.to run.with_params('fe80:0000:0000:0000:0204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80:0:0:0:204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80::204:61ff:fe9d:f156') } + it { is_expected.to run.with_params('fe80:0:0:0:0204:61ff:254.157.241.86') } + it { is_expected.to run.with_params('::1') } + it { is_expected.to run.with_params('fe80::') } + it { is_expected.to run.with_params('2001::') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('one').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0.256').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('0.0.0.0.0').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:2.3.4').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:257.1.2.3').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::ffff:12345678901234567890.1.26').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('affe:beef').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + it { is_expected.to run.with_params('::1', {}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', true).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 'one').and_raise_error(Puppet::ParseError, /is not a valid IPv6/) } + context 'unless running on ruby 1.8.7', :if => RUBY_VERSION != '1.8.7' do + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params('::1', 1).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_legacy_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_legacy_spec.rb new file mode 100644 index 0000000..7b48f12 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_legacy_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +if Puppet::Util::Package.versioncmp(Puppet.version, '4.4.0') >= 0 + describe 'validate_legacy' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + + describe 'when passing the type assertion and passing the previous validation' do + before do + scope.expects(:function_validate_foo).with([5]).once + Puppet.expects(:notice).never + end + it 'passes without notice' do + is_expected.to run.with_params('Integer', 'validate_foo', 5) + end + end + + describe 'when passing the type assertion and failing the previous validation' do + before do + scope.expects(:function_validate_foo).with([5]).raises(Puppet::ParseError, 'foo').once + Puppet.expects(:notice).with(includes('Accepting previously invalid value for target type')) + end + it 'passes with a notice about newly accepted value' do + is_expected.to run.with_params('Integer', 'validate_foo', 5) + end + end + + describe 'when failing the type assertion and passing the previous validation' do + before do + scope.expects(:function_validate_foo).with(['5']).once + subject.func.expects(:call_function).with('deprecation', 'validate_legacy', includes('Integer')).once + end + it 'passes with a deprecation message' do + is_expected.to run.with_params('Integer', 'validate_foo', '5') + end + end + + describe 'when failing the type assertion and failing the previous validation' do + before do + scope.expects(:function_validate_foo).with(['5']).raises(Puppet::ParseError, 'foo').once + subject.func.expects(:call_function).with('fail', includes('Integer')).once + end + it 'fails with a helpful message' do + is_expected.to run.with_params('Integer', 'validate_foo', '5') + end + end + + describe 'when passing in undef' do + before do + scope.expects(:function_validate_foo).with([:undef]).once + Puppet.expects(:notice).never + end + it 'works' do + is_expected.to run.with_params('Optional[Integer]', 'validate_foo', :undef) + end + end + + describe 'when passing in multiple arguments' do + before do + scope.expects(:function_validate_foo).with([:undef, 1, 'foo']).once + Puppet.expects(:notice).never + end + it 'passes with a deprecation message' do + is_expected.to run.with_params('Optional[Integer]', 'validate_foo', :undef, 1, 'foo') + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_numeric_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_numeric_spec.rb new file mode 100755 index 0000000..4c0e24d --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_numeric_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe 'validate_numeric' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params(3) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params(1, 2, 3, 4).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + [ true, 'true', false, 'false', 'iAmAString', '1test', '1 test', 'test 1', 'test 1 test', {}, { 'key' => 'value' }, { 1=> 2 }, '', :undef , 'x'].each do |invalid| + it { is_expected.to run.with_params(invalid).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + it { is_expected.to run.with_params(invalid, 10.0).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + it { is_expected.to run.with_params(invalid, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + end + + context 'when running on modern rubies', :unless => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError, /to be a Numeric/) } + end + + context 'when running on ruby, which munges hashes weirdly', :if => RUBY_VERSION == '1.8.7' do + it { is_expected.to run.with_params([0, 1, 2, {1=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + it { is_expected.to run.with_params([0, 1, 2, {0=>2}, 3, 4], 10, -10).and_raise_error(Puppet::ParseError) } + end + + it { is_expected.to run.with_params(1, '').and_raise_error(Puppet::ParseError, /to be unset or a Numeric/) } + it { is_expected.to run.with_params(1, 2, '').and_raise_error(Puppet::ParseError, /to be unset or a Numeric/) } + it { is_expected.to run.with_params(1, 2, 3).and_raise_error(Puppet::ParseError, /second argument to be larger than third argument/) } + end + + context 'with no range constraints' do + it { is_expected.to run.with_params(1) } + it { is_expected.to run.with_params(-1) } + it { is_expected.to run.with_params('1') } + it { is_expected.to run.with_params('-1') } + it { is_expected.to run.with_params([1, 2, 3, 4]) } + it { is_expected.to run.with_params([1, '2', '3', 4]) } + end + + context "with a maximum limit of 10.0" do + describe 'rejects numbers greater than the limit' do + it { is_expected.to run.with_params(11, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10.0,100], 10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'accepts numbers less or equal to the limit' do + it { is_expected.to run.with_params(10.0, 10.0) } + it { is_expected.to run.with_params(1, 10.0) } + it { is_expected.to run.with_params(-1, 10.0) } + it { is_expected.to run.with_params('1', 10.0) } + it { is_expected.to run.with_params('-1', 10.0) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10.0) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10.0) } + end + + context "with a minimum limit of -10.0" do + describe 'rejects numbers greater than the upper limit' do + it { is_expected.to run.with_params(11, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(100, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params(2**65, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + it { is_expected.to run.with_params([1,2,10.0,100], 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be smaller or equal/) } + end + + describe 'rejects numbers smaller than the lower limit' do + it { is_expected.to run.with_params(-11, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-100, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params(-2**65, 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + it { is_expected.to run.with_params([-10.0, 1,2,10.0,-100], 10.0, -10.0).and_raise_error(Puppet::ParseError, /to be greater or equal/) } + end + + describe 'accepts numbers between and including the limits' do + it { is_expected.to run.with_params(10.0, 10.0, -10.0) } + it { is_expected.to run.with_params(-10.0, 10.0, -10.0) } + it { is_expected.to run.with_params(1, 10.0, -10.0) } + it { is_expected.to run.with_params(-1, 10.0, -10.0) } + it { is_expected.to run.with_params('1', 10.0, -10.0) } + it { is_expected.to run.with_params('-1', 10.0, -10.0) } + it { is_expected.to run.with_params([1, 2, 3, 4], 10.0, -10.0) } + it { is_expected.to run.with_params([1, '2', '3', 4], 10.0, -10.0) } + end + end + end + + it { is_expected.to run.with_params(10.0, 10.0, 10.0) } + + describe 'empty upper limit is interpreted as infinity' do + it { is_expected.to run.with_params(11, '', 10.0) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_re_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_re_spec.rb new file mode 100755 index 0000000..3531182 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_re_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'validate_re' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('', '') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '', '', 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params('', '') } + it { is_expected.to run.with_params('', ['']) } + it { is_expected.to run.with_params('', [''], 'custom error') } + it { is_expected.to run.with_params('one', '^one') } + it { is_expected.to run.with_params('one', [ '^one', '^two' ]) } + it { is_expected.to run.with_params('one', [ '^one', '^two' ], 'custom error') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params('', []).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('one', 'two').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', 'two').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', ['two']).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('', ['two'], 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + it { is_expected.to run.with_params('notone', '^one').and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('notone', [ '^one', '^two' ]).and_raise_error(Puppet::ParseError, /does not match/) } + it { is_expected.to run.with_params('notone', [ '^one', '^two' ], 'custom error').and_raise_error(Puppet::ParseError, /custom error/) } + + describe 'non-string inputs' do + [ + 1, # Fixnum + 3.14, # Float + nil, # NilClass + true, # TrueClass + false, # FalseClass + ["10"], # Array + :key, # Symbol + {:key=>"val"}, # Hash + ].each do |input| + it { is_expected.to run.with_params(input, '.*').and_raise_error(Puppet::ParseError, /needs to be a String/) } + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_slength_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_slength_spec.rb new file mode 100755 index 0000000..e4162de --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_slength_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'validate_slength' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('1234567890', 10) + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', 2, 3, 'extra').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params('', '').and_raise_error(Puppet::ParseError, /second argument to be a positive Numeric/) } + it { is_expected.to run.with_params('', -1).and_raise_error(Puppet::ParseError, /second argument to be a positive Numeric/) } + it { is_expected.to run.with_params('', 1, '').and_raise_error(Puppet::ParseError, /third argument to be unset or a positive Numeric/) } + it { is_expected.to run.with_params('', 1, -1).and_raise_error(Puppet::ParseError, /third argument to be unset or a positive Numeric/) } + it { is_expected.to run.with_params('', 1, 2).and_raise_error(Puppet::ParseError, /argument to be equal to or larger than third argument/) } + end + + context "with a maximum length of 10" do + describe 'rejects strings longer than the limit' do + it { is_expected.to run.with_params('1234567890a', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params('1234567890abcdef', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params([ 'one', '1234567890abcdef' ], 10).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'accepts strings shorter or equal to the limit' do + it { is_expected.to run.with_params('1234567890', 10) } + it { is_expected.to run.with_params('12345', 10) } + it { is_expected.to run.with_params([ 'one', 'two' ], 10) } + end + + context "with a minimum length of 5" do + describe 'rejects strings longer than the upper limit' do + it { is_expected.to run.with_params('1234567890a', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params('1234567890abcdef', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'rejects numbers shorter than the lower limit' do + it { is_expected.to run.with_params('one', 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + it { is_expected.to run.with_params(['12345678', 'two'], 10, 5).and_raise_error(Puppet::ParseError, /Expected length/) } + end + + describe 'accepts strings of length between and including the limits' do + it { is_expected.to run.with_params('12345', 10, 5) } + it { is_expected.to run.with_params('123456', 10, 5) } + it { is_expected.to run.with_params('1234567', 10, 5) } + it { is_expected.to run.with_params('12345678', 10, 5) } + it { is_expected.to run.with_params('123456789', 10, 5) } + it { is_expected.to run.with_params('1234567890', 10, 5) } + it { is_expected.to run.with_params(['1233456', '12345678'], 10, 5) } + end + end + end + + describe 'corner cases' do + it { pending('this should work'); is_expected.to run.with_params('', 0, 0) } + it { is_expected.to run.with_params('1234567890', 10, 10) } + end + + describe 'empty upper limit is interpreted as infinity' do + it { pending('not implemented'); is_expected.to run.with_params('1234567890ab', '', 10) } + it { pending('not implemented'); is_expected.to run.with_params('12345678', '', 10).and_raise_error(Puppet::ParseError, /Expected length/) } + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_string_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_string_spec.rb new file mode 100755 index 0000000..0907ede --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_string_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'validate_string' do + after(:all) do + ENV.delete('STDLIB_LOG_DEPRECATIONS') + end + + # Checking for deprecation warning + it 'should display a single deprecation' do + ENV['STDLIB_LOG_DEPRECATIONS'] = "true" + scope.expects(:warning).with(includes('This method is deprecated')) + is_expected.to run.with_params('', '') + end + + describe 'signature validation' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + + describe 'valid inputs' do + it { is_expected.to run.with_params('') } + it { is_expected.to run.with_params(nil) } + it { is_expected.to run.with_params('one') } + it { is_expected.to run.with_params('one', 'two') } + end + + describe 'invalid inputs' do + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(1).and_raise_error(Puppet::ParseError, /is not a string/) } + it { is_expected.to run.with_params(true).and_raise_error(Puppet::ParseError, /is not a string/) } + end + end +end diff --git a/environments/production/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb b/environments/production/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb new file mode 100755 index 0000000..eb63310 --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/validate_x509_rsa_key_pair_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' + +describe 'validate_x509_rsa_key_pair' do + + let(:valid_cert) do + < 'value' }).and_return(['value']) } + it 'should return the array of values' do + result = subject.call([{ 'key1' => 'value1', 'key2' => 'value2', 'duplicate_value_key' => 'value2' }]) + expect(result).to match_array(['value1', 'value2', 'value2']) + end + + it 'should run with UTF8 and double byte characters' do + result = subject.call([{ 'かぎ' => '使用', 'ҝĕұ' => '√ẩŀứệ', 'ҝĕұďŭрļǐçằťè' => '√ẩŀứệ' }]) + expect(result).to match_array(['使用', '√ẩŀứệ', '√ẩŀứệ']) + end +end diff --git a/environments/production/modules/stdlib/spec/functions/zip_spec.rb b/environments/production/modules/stdlib/spec/functions/zip_spec.rb new file mode 100755 index 0000000..e1ae8ee --- /dev/null +++ b/environments/production/modules/stdlib/spec/functions/zip_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'zip' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) } + it { + pending("Current implementation ignores parameters after the third.") + is_expected.to run.with_params([], [], true, []).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) + } + it { is_expected.to run.with_params([], []).and_return([]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6]).and_return([[1,4], [2,5], [3,6]]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6], false).and_return([[1,4], [2,5], [3,6]]) } + it { is_expected.to run.with_params([1,2,3], [4,5,6], true).and_return([1, 4, 2, 5, 3, 6]) } + + context 'should run with UTF8 and double byte characters' do + it { is_expected.to run.with_params(['ầ', 'ь', 'ć'], ['đ', 'ề', 'ƒ']).and_return([['ầ','đ'], ['ь','ề'], ['ć', 'ƒ']]) } + it { is_expected.to run.with_params(['ペ', '含', '値'], ['ッ', '文', 'イ']).and_return([['ペ','ッ'], ['含','文'], ['値', 'イ']]) } + end +end + diff --git a/environments/production/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb b/environments/production/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb new file mode 100755 index 0000000..505e240 --- /dev/null +++ b/environments/production/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb @@ -0,0 +1,9 @@ +#! /usr/bin/env ruby -S rspec +require 'rspec' + +class Object + # This is necessary because the RAL has a 'should' + # method. + alias :must :should + alias :must_not :should_not +end diff --git a/environments/production/modules/stdlib/spec/monkey_patches/publicize_methods.rb b/environments/production/modules/stdlib/spec/monkey_patches/publicize_methods.rb new file mode 100755 index 0000000..3ae59f9 --- /dev/null +++ b/environments/production/modules/stdlib/spec/monkey_patches/publicize_methods.rb @@ -0,0 +1,11 @@ +#! /usr/bin/env ruby -S rspec +# Some monkey-patching to allow us to test private methods. +class Class + def publicize_methods(*methods) + saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods + + self.class_eval { public(*saved_private_instance_methods) } + yield + self.class_eval { private(*saved_private_instance_methods) } + end +end diff --git a/environments/production/modules/stdlib/spec/spec_helper.rb b/environments/production/modules/stdlib/spec/spec_helper.rb new file mode 100755 index 0000000..22d5d68 --- /dev/null +++ b/environments/production/modules/stdlib/spec/spec_helper.rb @@ -0,0 +1,8 @@ +#This file is generated by ModuleSync, do not edit. +require 'puppetlabs_spec_helper/module_spec_helper' + +# put local configuration and setup into spec_helper_local +begin + require 'spec_helper_local' +rescue LoadError +end diff --git a/environments/production/modules/stdlib/spec/spec_helper_acceptance.rb b/environments/production/modules/stdlib/spec/spec_helper_acceptance.rb new file mode 100755 index 0000000..4d85e7d --- /dev/null +++ b/environments/production/modules/stdlib/spec/spec_helper_acceptance.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'puppet' +require 'beaker-rspec' +require 'beaker/puppet_install_helper' +require 'beaker/module_install_helper' + +run_puppet_install_helper +install_ca_certs unless ENV['PUPPET_INSTALL_TYPE'] =~ /pe/i +install_module_on(hosts) +install_module_dependencies_on(hosts) + +RSpec.configure do |c| + # Readable test descriptions + c.formatter = :documentation + + # Configure all nodes in nodeset + c.before :suite do + end +end + +def get_puppet_version + (on default, puppet('--version')).output.chomp +end + +RSpec.shared_context "with faked facts" do + let(:facts_d) do + puppet_version = get_puppet_version + if fact('osfamily') =~ /windows/i + if fact('kernelmajversion').to_f < 6.0 + 'C:/Documents and Settings/All Users/Application Data/PuppetLabs/facter/facts.d' + else + 'C:/ProgramData/PuppetLabs/facter/facts.d' + end + elsif Puppet::Util::Package.versioncmp(puppet_version, '4.0.0') < 0 and fact('is_pe', '--puppet') == "true" + '/etc/puppetlabs/facter/facts.d' + else + '/etc/facter/facts.d' + end + end + + before :each do + #No need to create on windows, PE creates by default + if fact('osfamily') !~ /windows/i + shell("mkdir -p '#{facts_d}'") + end + end + + after :each do + shell("rm -f '#{facts_d}/fqdn.txt'", :acceptable_exit_codes => [0,1]) + end + + def fake_fact(name, value) + shell("echo #{name}=#{value} > '#{facts_d}/#{name}.txt'") + end +end diff --git a/environments/production/modules/stdlib/spec/spec_helper_local.rb b/environments/production/modules/stdlib/spec/spec_helper_local.rb new file mode 100644 index 0000000..616490c --- /dev/null +++ b/environments/production/modules/stdlib/spec/spec_helper_local.rb @@ -0,0 +1,31 @@ +# automatically load any shared examples or contexts +Dir["./spec/support/**/*.rb"].sort.each { |f| require f } + +# hack to enable all the expect syntax (like allow_any_instance_of) in rspec-puppet examples +RSpec::Mocks::Syntax.enable_expect(RSpec::Puppet::ManifestMatchers) + +RSpec.configure do |config| + # supply tests with a possibility to test for the future parser + config.add_setting :puppet_future + config.puppet_future = Puppet.version.to_f >= 4.0 + + config.before :each do + # Ensure that we don't accidentally cache facts and environment between + # test cases. This requires each example group to explicitly load the + # facts being exercised with something like + # Facter.collection.loader.load(:ipaddress) + Facter.clear + Facter.clear_messages + + RSpec::Mocks.setup + end + + config.after :each do + RSpec::Mocks.verify + RSpec::Mocks.teardown + end +end + +# Helper class to test handling of arguments which are derived from string +class AlsoString < String +end diff --git a/environments/production/modules/stdlib/spec/support/shared_data.rb b/environments/production/modules/stdlib/spec/support/shared_data.rb new file mode 100644 index 0000000..ea9b7a0 --- /dev/null +++ b/environments/production/modules/stdlib/spec/support/shared_data.rb @@ -0,0 +1,38 @@ +module SharedData + IPV4_PATTERNS = [ + '0.0.0.0', + '1.2.3.4', + '10.10.10.10', + '127.0.0.1', + '192.88.99.0', + '194.232.104.150', + '224.0.0.0', + '244.24.24.24', + '255.255.255.255', + '8.8.8.8', + '8.8.8.8/0', + '8.8.8.8/16', + '8.8.8.8/255.255.0.0', + '8.8.8.8/32', + ] + IPV4_NEGATIVE_PATTERNS = [ + '', + '0000', + '0.0.0.0.', + '0.0.0.0./0.0.0.0.', + '0.0.0.0./1', + '0.0.0.0.0', + '0.0.0.0/0.0.0.0.', + '0.0.0.256', + '0.0.0', + '1.2.3.4.5', + '1.2.3', + '10.010.10.10', + '2001:0db8:85a3:0000:0000:8a2e:0370:73342001:0db8:85a3:0000:0000:8a2e:0370:7334', + '4.4.4', + '77', + '9999.9999.9999.9999', + 'affe::beef', + 'nope', + ] +end diff --git a/environments/production/modules/stdlib/spec/unit/ensure_resources_spec.rb b/environments/production/modules/stdlib/spec/unit/ensure_resources_spec.rb new file mode 100644 index 0000000..aea723e --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/ensure_resources_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'test::ensure_resources', type: :class do + let(:params) {{ resource_type: 'user', title_hash: title_param, attributes_hash: {'ensure' => 'present'} }} + + describe 'given a title hash of multiple resources' do + + let(:title_param) { {'dan' => { 'gid' => 'mygroup', 'uid' => '600' }, 'alex' => { 'gid' => 'mygroup', 'uid' => '700'}} } + + it { is_expected.to compile } + it { is_expected.to contain_user('dan').with({ 'gid' => 'mygroup', 'uid' => '600', 'ensure' => 'present'}) } + it { is_expected.to contain_user('alex').with({ 'gid' => 'mygroup', 'uid' => '700', 'ensure' => 'present'}) } + end + + describe 'given a title hash of a single resource' do + + let(:title_param) { {'dan' => { 'gid' => 'mygroup', 'uid' => '600' }} } + + it { is_expected.to compile } + it { is_expected.to contain_user('dan').with({ 'gid' => 'mygroup', 'uid' => '600', 'ensure' => 'present'}) } + end +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb new file mode 100755 index 0000000..0afadb2 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb @@ -0,0 +1,32 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/facter_dot_d' + +describe Facter::Util::DotD do + + context 'returns a simple fact' do + before :each do + Facter.stubs(:version).returns('1.6.1') + subject.stubs(:entries).returns(['/etc/facter/facts.d/fake_fact.txt']) + File.stubs(:readlines).with('/etc/facter/facts.d/fake_fact.txt').returns(['fake_fact=fake fact']) + subject.create + end + + it 'should return successfully' do + expect(Facter.fact(:fake_fact).value).to eq('fake fact') + end + end + + context 'returns a fact with equals signs' do + before :each do + Facter.stubs(:version).returns('1.6.1') + subject.stubs(:entries).returns(['/etc/facter/facts.d/foo.txt']) + File.stubs(:readlines).with('/etc/facter/facts.d/foo.txt').returns(['foo=1+1=2']) + subject.create + end + + it 'should return successfully' do + expect(Facter.fact(:foo).value).to eq('1+1=2') + end + end +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/package_provider_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/package_provider_spec.rb new file mode 100644 index 0000000..3954faf --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/package_provider_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'puppet/type' +require 'puppet/type/package' + +describe 'package_provider', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + ['4.2.2', '3.7.1 (Puppet Enterprise 3.2.1)'].each do |puppetversion| + describe "on puppet ''#{puppetversion}''" do + before :each do + Facter.stubs(:value).returns puppetversion + end + + context "darwin" do + it "should return pkgdmg" do + provider = Puppet::Type.type(:package).provider(:pkgdmg) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('pkgdmg') + end + end + + context "centos 7" do + it "should return yum" do + provider = Puppet::Type.type(:package).provider(:yum) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('yum') + end + end + + context "ubuntu" do + it "should return apt" do + provider = Puppet::Type.type(:package).provider(:apt) + Puppet::Type.type(:package).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:package_provider).value).to eq('apt') + end + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/pe_version_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/pe_version_spec.rb new file mode 100755 index 0000000..c11a1cd --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/pe_version_spec.rb @@ -0,0 +1,88 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "PE Version specs" do + before :each do + # Explicitly load the pe_version.rb file which contains generated facts + # that cannot be automatically loaded. Puppet 2.x implements + # Facter.collection.load while Facter 1.x markes Facter.collection.load as + # a private method. + if Facter.collection.respond_to? :load + Facter.collection.load(:pe_version) + else + Facter.collection.loader.load(:pe_version) + end + end + + context "When puppetversion is nil" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns(nil) + end + + it "pe_version is nil" do + expect(Facter.fact(:puppetversion).value).to be_nil + expect(Facter.fact(:pe_version).value).to be_nil + end + end + + context "If PE is installed" do + %w{ 2.6.1 2.10.300 }.each do |version| + puppetversion = "2.7.19 (Puppet Enterprise #{version})" + context "puppetversion => #{puppetversion}" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns(puppetversion) + end + + (major,minor,patch) = version.split(".") + + it "Should return true" do + expect(Facter.fact(:is_pe).value).to eq(true) + end + + it "Should have a version of #{version}" do + expect(Facter.fact(:pe_version).value).to eq(version) + end + + it "Should have a major version of #{major}" do + expect(Facter.fact(:pe_major_version).value).to eq(major) + end + + it "Should have a minor version of #{minor}" do + expect(Facter.fact(:pe_minor_version).value).to eq(minor) + end + + it "Should have a patch version of #{patch}" do + expect(Facter.fact(:pe_patch_version).value).to eq(patch) + end + end + end + end + + context "When PE is not installed" do + before :each do + Facter.fact(:puppetversion).stubs(:value).returns("2.7.19") + end + + it "is_pe is false" do + expect(Facter.fact(:is_pe).value).to eq(false) + end + + it "pe_version is nil" do + expect(Facter.fact(:pe_version).value).to be_nil + end + + it "pe_major_version is nil" do + expect(Facter.fact(:pe_major_version).value).to be_nil + end + + it "pe_minor_version is nil" do + expect(Facter.fact(:pe_minor_version).value).to be_nil + end + + it "Should have a patch version" do + expect(Facter.fact(:pe_patch_version).value).to be_nil + end + end + +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/root_home_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/root_home_spec.rb new file mode 100755 index 0000000..a5c2846 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/root_home_spec.rb @@ -0,0 +1,65 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/root_home' + +describe Facter::Util::RootHome do + context "solaris" do + let(:root_ent) { "root:x:0:0:Super-User:/:/sbin/sh" } + let(:expected_root_home) { "/" } + + it "should return /" do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent) + expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home) + end + end + context "linux" do + let(:root_ent) { "root:x:0:0:root:/root:/bin/bash" } + let(:expected_root_home) { "/root" } + + it "should return /root" do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent) + expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home) + end + end + context "windows" do + before :each do + Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(nil) + end + it "should be nil on windows" do + expect(Facter::Util::RootHome.get_root_home).to be_nil + end + end +end + +describe 'root_home', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + context "macosx" do + before do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:osfamily).stubs(:value).returns("Darwin") + end + let(:expected_root_home) { "/var/root" } + sample_dscacheutil = File.read(fixtures('dscacheutil','root')) + + it "should return /var/root" do + Facter::Util::Resolution.stubs(:exec).with("dscacheutil -q user -a name root").returns(sample_dscacheutil) + expect(Facter.fact(:root_home).value).to eq(expected_root_home) + end + end + + context "aix" do + before do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter.fact(:osfamily).stubs(:value).returns("AIX") + end + let(:expected_root_home) { "/root" } + sample_lsuser = File.read(fixtures('lsuser','root')) + + it "should return /root" do + Facter::Util::Resolution.stubs(:exec).with("lsuser -c -a home root").returns(sample_lsuser) + expect(Facter.fact(:root_home).value).to eq(expected_root_home) + end + end +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/service_provider_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/service_provider_spec.rb new file mode 100644 index 0000000..ad8a5fc --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/service_provider_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'puppet/type' +require 'puppet/type/service' + +describe 'service_provider', :type => :fact do + before { Facter.clear } + after { Facter.clear } + + context "macosx" do + it "should return launchd" do + provider = Puppet::Type.type(:service).provider(:launchd) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('launchd') + end + end + + context "systemd" do + it "should return systemd" do + provider = Puppet::Type.type(:service).provider(:systemd) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('systemd') + end + end + + context "redhat" do + it "should return redhat" do + provider = Puppet::Type.type(:service).provider(:redhat) + Puppet::Type.type(:service).stubs(:defaultprovider).returns provider + + expect(Facter.fact(:service_provider).value).to eq('redhat') + end + end + +end diff --git a/environments/production/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb b/environments/production/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb new file mode 100755 index 0000000..c278b79 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb @@ -0,0 +1,37 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter/util/puppet_settings' + +describe Facter::Util::PuppetSettings do + + describe "#with_puppet" do + context "Without Puppet loaded" do + before(:each) do + Module.expects(:const_get).with("Puppet").raises(NameError) + end + + it 'should be nil' do + expect(subject.with_puppet { Puppet[:vardir] }).to be_nil + end + it 'should not yield to the block' do + Puppet.expects(:[]).never + expect(subject.with_puppet { Puppet[:vardir] }).to be_nil + end + end + context "With Puppet loaded" do + module Puppet; end + let(:vardir) { "/var/lib/puppet" } + + before :each do + Puppet.expects(:[]).with(:vardir).returns vardir + end + + it 'should yield to the block' do + subject.with_puppet { Puppet[:vardir] } + end + it 'should return the nodes vardir' do + expect(subject.with_puppet { Puppet[:vardir] }).to eq vardir + end + end + end +end diff --git a/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb b/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb new file mode 100644 index 0000000..b162127 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/enclose_ipv6_spec.rb @@ -0,0 +1,69 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the enclose_ipv6 function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + expect(Puppet::Parser::Functions.function("enclose_ipv6")).to eq("function_enclose_ipv6") + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_enclose_ipv6([]) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError if there is more than 1 arguments" do + expect { scope.function_enclose_ipv6(['argument1','argument2']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given garbage" do + expect { scope.function_enclose_ipv6(['garbage']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given something else than a string or an array" do + expect { scope.function_enclose_ipv6([['1' => '127.0.0.1']]) }.to( raise_error(Puppet::ParseError) ) + end + + it "should not raise a ParseError when given a single ip string" do + expect { scope.function_enclose_ipv6(['127.0.0.1']) }.to_not raise_error + end + + it "should not raise a ParseError when given * as ip string" do + expect { scope.function_enclose_ipv6(['*']) }.to_not raise_error + end + + it "should not raise a ParseError when given an array of ip strings" do + expect { scope.function_enclose_ipv6([['127.0.0.1','fe80::1']]) }.to_not raise_error + end + + it "should not raise a ParseError when given differently notations of ip addresses" do + expect { scope.function_enclose_ipv6([['127.0.0.1','fe80::1','[fe80::1]']]) }.to_not raise_error + end + + it "should raise a ParseError when given a wrong ipv4 address" do + expect { scope.function_enclose_ipv6(['127..0.0.1']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given a ipv4 address with square brackets" do + expect { scope.function_enclose_ipv6(['[127.0.0.1]']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should raise a ParseError when given a wrong ipv6 address" do + expect { scope.function_enclose_ipv6(['fe80:::1']) }.to( raise_error(Puppet::ParseError) ) + end + + it "should embrace ipv6 adresses within an array of ip addresses" do + result = scope.function_enclose_ipv6([['127.0.0.1','fe80::1','[fe80::2]']]) + expect(result).to(eq(['127.0.0.1','[fe80::1]','[fe80::2]'])) + end + + it "should embrace a single ipv6 adresse" do + result = scope.function_enclose_ipv6(['fe80::1']) + expect(result).to(eq(['[fe80::1]'])) + end + + it "should not embrace a single ipv4 adresse" do + result = scope.function_enclose_ipv6(['127.0.0.1']) + expect(result).to(eq(['127.0.0.1'])) + end +end diff --git a/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb b/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb new file mode 100644 index 0000000..8931208 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/puppet/parser/functions/is_absolute_path_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe :is_absolute_path do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + let(:function_args) do + [] + end + + let(:function) do + scope.function_is_absolute_path(function_args) + end + + + describe 'validate arity' do + let(:function_args) do + [1,2] + end + it "should raise a ParseError if there is more than 1 arguments" do + lambda { function }.should( raise_error(ArgumentError)) + end + + end + + it "should exist" do + Puppet::Parser::Functions.function(subject).should == "function_#{subject}" + end + + # help enforce good function defination + it 'should contain arity' do + + end + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { function }.should( raise_error(ArgumentError)) + end + + + describe 'should retrun true' do + let(:return_value) do + true + end + + describe 'windows' do + let(:function_args) do + ['c:\temp\test.txt'] + end + it 'should return data' do + function.should eq(return_value) + end + end + + describe 'non-windows' do + let(:function_args) do + ['/temp/test.txt'] + end + + it 'should return data' do + function.should eq(return_value) + end + end + end + + describe 'should return false' do + let(:return_value) do + false + end + describe 'windows' do + let(:function_args) do + ['..\temp\test.txt'] + end + it 'should return data' do + function.should eq(return_value) + end + end + + describe 'non-windows' do + let(:function_args) do + ['../var/lib/puppet'] + end + it 'should return data' do + function.should eq(return_value) + end + end + end +end \ No newline at end of file diff --git a/environments/production/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb b/environments/production/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb new file mode 100755 index 0000000..1f41f62 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb @@ -0,0 +1,447 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'tempfile' +provider_class = Puppet::Type.type(:file_line).provider(:ruby) +describe provider_class do + context "when adding" do + let :tmpfile do + tmp = Tempfile.new('tmp') + path = tmp.path + tmp.close! + path + end + let :resource do + Puppet::Type::File_line.new( + {:name => 'foo', :path => tmpfile, :line => 'foo'} + ) + end + let :provider do + provider_class.new(resource) + end + + it 'should detect if the line exists in the file' do + File.open(tmpfile, 'w') do |fh| + fh.write('foo') + end + expect(provider.exists?).to be_truthy + end + it 'should detect if the line does not exist in the file' do + File.open(tmpfile, 'w') do |fh| + fh.write('foo1') + end + expect(provider.exists?).to be_nil + end + it 'should append to an existing file when creating' do + provider.create + expect(File.read(tmpfile).chomp).to eq('foo') + end + end + context 'when using replace' do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :replace => false, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should not replace the matching line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo3") + end + expect(@provider.exists?).to be_truthy + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo=blah\nfoo2\nfoo3") + end + + it 'should append the line if no matches are found' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo2\nfoo = bar") + end + + it 'should raise an error with invalid values' do + expect { + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :replace => 'asgadga', + } + ) + }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + end + end + context "when matching" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + } + ) + @provider = provider_class.new(@resource) + end + + describe 'using match' do + it 'should raise an error if more than one line matches, and should not have modified the file' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + expect(@provider.exists?).to be_nil + expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) + expect(File.read(@tmpfile)).to eql("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + + it 'should replace all lines that matches' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :multiple => true, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2\nfoo = bar") + end + + it 'should raise an error with invalid values' do + expect { + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo = bar', + :match => '^foo\s*=.*$', + :multiple => 'asgadga', + } + ) + }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./) + end + + it 'should replace a line that matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo=blah\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + end + it 'should add a new line if no lines match' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2") + end + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo = bar\n") + end + it 'should do nothing if the exact line already exists' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = bar\nfoo2") + end + expect(@provider.exists?).to be_truthy + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2") + end + end + + describe 'using after' do + let :resource do + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => '^foo1', + } + ) + end + + let :provider do + provider_class.new(resource) + end + context 'match and after set' do + shared_context 'resource_create' do + let(:match) { '^foo2$' } + let(:after) { '^foo1$' } + let(:resource) { + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => after, + :match => match, + } + ) + } + end + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo2\nfoo = baz") + end + end + describe 'inserts at match' do + include_context 'resource_create' + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo = baz") + } + end + describe 'inserts a new line after when no match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } + end + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\ninserted = line\nfoo2\nfoo = baz") + } + end + describe 'append to end of file if no match for both after and match' do + include_context 'resource_create' do + let(:match) { '^nevergoingtomatch$' } + let(:after) { '^stillneverafter' } + end + it { + provider.create + expect(File.read(@tmpfile).chomp).to eq("foo1\nfoo2\nfoo = baz\ninserted = line") + } + end + end + context 'with one line matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") + end + end + + it 'inserts the specified line after the line matching the "after" expression' do + provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz") + end + end + + context 'with multiple lines matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz") + end + end + + it 'errors out stating "One or no line must match the pattern"' do + expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/) + end + + it 'adds the line after all lines matching the after expression' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => '^foo1$', + :multiple => true, + } + ) + @provider = provider_class.new(@resource) + expect(@provider.exists?).to be_nil + @provider.create + expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo1\ninserted = line\nfoo = baz") + end + end + + context 'with no lines matching the after expression' do + let :content do + "foo3\nfoo = blah\nfoo2\nfoo = baz\n" + end + + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write(content) + end + end + + it 'appends the specified line to the file' do + provider.create + expect(File.read(@tmpfile)).to eq(content << resource[:line] << "\n") + end + end + end + end + + context "when removing" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo', + :ensure => 'absent', + } + ) + @provider = provider_class.new(@resource) + end + it 'should remove the line if it exists' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + end + + it 'should remove the line without touching the last new line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\n") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + + it 'should remove any occurence of the line' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + end + + context "when removing with a match" do + before :each do + # TODO: these should be ported over to use the PuppetLabs spec_helper + # file fixtures once the following pull request has been merged: + # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files + tmp = Tempfile.new('tmp') + @tmpfile = tmp.path + tmp.close! + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :match_for_absence => true, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should find a line to match' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + expect(@provider.exists?).to be_truthy + end + + it 'should remove one line if it matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2") + end + + it 'should raise an error if more than one line matches' do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + expect { @provider.destroy }.to raise_error(Puppet::Error, /More than one line/) + end + + it 'should remove multiple lines if :multiple is true' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :multiple => true, + :match_for_absence => true, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2\nfoo\nfoo") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n") + end + + it 'should ignore the match if match_for_absence is not specified' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + end + + it 'should ignore the match if match_for_absence is false' do + @resource = Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'foo2', + :ensure => 'absent', + :match => 'o$', + :match_for_absence => false, + } + ) + @provider = provider_class.new(@resource) + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo\nfoo2") + end + @provider.destroy + expect(File.read(@tmpfile)).to eql("foo1\nfoo\n") + end + + end + +end diff --git a/environments/production/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb b/environments/production/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb new file mode 100755 index 0000000..c738a27 --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +anchor = Puppet::Type.type(:anchor).new(:name => "ntp::begin") + +describe anchor do + it "should stringify normally" do + expect(anchor.to_s).to eq("Anchor[ntp::begin]") + end +end diff --git a/environments/production/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb b/environments/production/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb new file mode 100755 index 0000000..150149b --- /dev/null +++ b/environments/production/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb @@ -0,0 +1,113 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'tempfile' +describe Puppet::Type.type(:file_line) do + let :tmp_path do + if Puppet::Util::Platform.windows? + 'C:\tmp\path' + else + '/tmp/path' + end + end + let :my_path do + if Puppet::Util::Platform.windows? + 'C:\my\path' + else + '/my/path' + end + end + let :file_line do + Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'line', :path => tmp_path) + end + it 'should accept a line and path' do + file_line[:line] = 'my_line' + expect(file_line[:line]).to eq('my_line') + file_line[:path] = my_path + expect(file_line[:path]).to eq(my_path) + end + it 'should accept a match regex' do + file_line[:match] = '^foo.*$' + expect(file_line[:match]).to eq('^foo.*$') + end + it 'should accept a match regex that does not match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => my_path, + :line => 'foo=bar', + :match => '^bar=blah$' + )}.not_to raise_error + end + it 'should accept a match regex that does match the specified line' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'foo', + :path => my_path, + :line => 'foo=bar', + :match => '^\s*foo=.*$' + )}.not_to raise_error + end + it 'should accept utf8 characters' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'ƒồỗ', + :path => my_path, + :line => 'ƒồỗ=ьåя', + :match => '^ьåя=βļάħ$' + )}.not_to raise_error + end + it 'should accept double byte characters' do + expect { + Puppet::Type.type(:file_line).new( + :name => 'フーバー', + :path => my_path, + :line => 'この=それ', + :match => '^この=ああ$' + )}.not_to raise_error + end + it 'should accept posix filenames' do + file_line[:path] = tmp_path + expect(file_line[:path]).to eq(tmp_path) + end + it 'should not accept unqualified path' do + expect { file_line[:path] = 'file' }.to raise_error(Puppet::Error, /File paths must be fully qualified/) + end + it 'should require that a line is specified' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path) }.to raise_error(Puppet::Error, /line is a required attribute/) + end + it 'should not require that a line is specified when matching for absence' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path, :ensure => :absent, :match_for_absence => :true, :match => 'match') }.not_to raise_error + end + it 'should require that a file is specified' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'path') }.to raise_error(Puppet::Error, /path is a required attribute/) + end + it 'should default to ensure => present' do + expect(file_line[:ensure]).to eq :present + end + it 'should default to replace => true' do + expect(file_line[:replace]).to eq :true + end + it 'should default to encoding => UTF-8' do + expect(file_line[:encoding]).to eq 'UTF-8' + end + it 'should accept encoding => iso-8859-1' do + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => tmp_path, :ensure => :present, :encoding => 'iso-8859-1', :line => 'bar') }.not_to raise_error + end + it "should autorequire the file it manages" do + catalog = Puppet::Resource::Catalog.new + file = Puppet::Type.type(:file).new(:name => tmp_path) + catalog.add_resource file + catalog.add_resource file_line + + relationship = file_line.autorequire.find do |rel| + (rel.source.to_s == "File[#{tmp_path}]") and (rel.target.to_s == file_line.to_s) + end + expect(relationship).to be_a Puppet::Relationship + end + + it "should not autorequire the file it manages if it is not managed" do + catalog = Puppet::Resource::Catalog.new + catalog.add_resource file_line + expect(file_line.autorequire).to be_empty + end +end diff --git a/environments/production/modules/stdlib/types/absolutepath.pp b/environments/production/modules/stdlib/types/absolutepath.pp new file mode 100644 index 0000000..70ec916 --- /dev/null +++ b/environments/production/modules/stdlib/types/absolutepath.pp @@ -0,0 +1,2 @@ +# A strict absolutepath type +type Stdlib::Absolutepath = Variant[Stdlib::Windowspath, Stdlib::Unixpath] diff --git a/environments/production/modules/stdlib/types/compat/absolute_path.pp b/environments/production/modules/stdlib/types/compat/absolute_path.pp new file mode 100644 index 0000000..d11784e --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/absolute_path.pp @@ -0,0 +1,7 @@ +# Emulate the is_absolute_path and validate_absolute_path functions +# +# The first pattern is originally from is_absolute_path, which had it from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path? +# slash = '[\\\\/]' +# name = '[^\\\\/]+' +# %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, +type Stdlib::Compat::Absolute_path = Variant[Pattern[/^(([a-zA-Z]:[\\\/])|([\\\/][\\\/][^\\\/]+[\\\/][^\\\/]+)|([\\\/][\\\/]\?[\\\/][^\\\/]+))/], Pattern[/^\//]] diff --git a/environments/production/modules/stdlib/types/compat/array.pp b/environments/production/modules/stdlib/types/compat/array.pp new file mode 100644 index 0000000..ba65dc4 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/array.pp @@ -0,0 +1,2 @@ +# Emulate the is_array and validate_array functions +type Stdlib::Compat::Array = Array[Any] diff --git a/environments/production/modules/stdlib/types/compat/bool.pp b/environments/production/modules/stdlib/types/compat/bool.pp new file mode 100644 index 0000000..5d8e27e --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/bool.pp @@ -0,0 +1,2 @@ +# Emulate the is_bool and validate_bool functions +type Stdlib::Compat::Bool = Boolean diff --git a/environments/production/modules/stdlib/types/compat/float.pp b/environments/production/modules/stdlib/types/compat/float.pp new file mode 100644 index 0000000..7f98bd2 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/float.pp @@ -0,0 +1,19 @@ +# Emulate the is_float function +# The regex is what's currently used in is_float +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_float($value,) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Float $value) { +# validate_float($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Float = Variant[Float, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)(?:[eE]-?\d+)?$/]] diff --git a/environments/production/modules/stdlib/types/compat/hash.pp b/environments/production/modules/stdlib/types/compat/hash.pp new file mode 100644 index 0000000..e84a10b --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/hash.pp @@ -0,0 +1,2 @@ +# Emulate the is_hash and validate_hash functions +type Stdlib::Compat::Hash = Hash[Any, Any] diff --git a/environments/production/modules/stdlib/types/compat/integer.pp b/environments/production/modules/stdlib/types/compat/integer.pp new file mode 100644 index 0000000..e5cadb6 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/integer.pp @@ -0,0 +1,23 @@ +# Emulate the is_integer and validate_integer functions +# The regex is what's currently used in is_integer +# validate_numeric also allows range checking, which cannot be mapped to the string parsing inside the function. +# For full backwards compatibility, you will need to keep the validate_numeric call around to catch everything. +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_integer($value, 10, 0) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Integer $value) { +# validate_numeric($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# > Note that you need to use Variant[Integer[0, 10], Float[0, 10]] if you want to match both integers and floating point numbers. +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Integer = Variant[Integer, Pattern[/^-?(?:(?:[1-9]\d*)|0)$/], Array[Variant[Integer, Pattern[/^-?(?:(?:[1-9]\d*)|0)$/]]]] diff --git a/environments/production/modules/stdlib/types/compat/ip_address.pp b/environments/production/modules/stdlib/types/compat/ip_address.pp new file mode 100644 index 0000000..bf4c4b4 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/ip_address.pp @@ -0,0 +1 @@ +type Stdlib::Compat::Ip_address = Variant[Stdlib::Compat::Ipv4, Stdlib::Compat::Ipv6] diff --git a/environments/production/modules/stdlib/types/compat/ipv4.pp b/environments/production/modules/stdlib/types/compat/ipv4.pp new file mode 100644 index 0000000..a0ba0d6 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/ipv4.pp @@ -0,0 +1,2 @@ +# Emulate the validate_ipv4_address and is_ipv4_address functions +type Stdlib::Compat::Ipv4 = Pattern[/^((([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))[.]){3}([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d)))(\/((([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))[.]){3}([0-9](?!\d)|[1-9][0-9](?!\d)|1[0-9]{2}(?!\d)|2[0-4][0-9](?!\d)|25[0-5](?!\d))|[0-9]+))?$/] diff --git a/environments/production/modules/stdlib/types/compat/ipv6.pp b/environments/production/modules/stdlib/types/compat/ipv6.pp new file mode 100644 index 0000000..8b82f1a --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/ipv6.pp @@ -0,0 +1 @@ +type Stdlib::Compat::Ipv6 = Pattern[/\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/] diff --git a/environments/production/modules/stdlib/types/compat/numeric.pp b/environments/production/modules/stdlib/types/compat/numeric.pp new file mode 100644 index 0000000..5bfc3d3 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/numeric.pp @@ -0,0 +1,23 @@ +# Emulate the is_numeric and validate_numeric functions +# The regex is what's currently used in is_numeric +# validate_numeric also allows range checking, which cannot be mapped to the string parsing inside the function. +# For full backwards compatibility, you will need to keep the validate_numeric call around to catch everything. +# To keep your development moving forward, you can also add a deprecation warning using the Integer type: +# +# ```class example($value) { validate_numeric($value, 10, 0) }``` +# +# would turn into +# +# ``` +# class example(Stdlib::Compat::Numeric $value) { +# validate_numeric($value, 10, 0) +# assert_type(Integer[0, 10], $value) |$expected, $actual| { +# warning("The 'value' parameter for the 'ntp' class has type ${actual}, but should be ${expected}.") +# } +# } +# ``` +# +# > Note that you need to use Variant[Integer[0, 10], Float[0, 10]] if you want to match both integers and floating point numbers. +# +# This allows you to find all places where a consumers of your code call it with unexpected values. +type Stdlib::Compat::Numeric = Variant[Numeric, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$/], Array[Variant[Numeric, Pattern[/^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$/]]]] diff --git a/environments/production/modules/stdlib/types/compat/re.pp b/environments/production/modules/stdlib/types/compat/re.pp new file mode 100644 index 0000000..e4b5f30 --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/re.pp @@ -0,0 +1,3 @@ +# Emulate the validate_re function +# validate_re(value, re) translates to Pattern[re], which is not directly mappable as a type alias, but can be specified as Pattern[re]. +# Therefore this needs to be translated directly. diff --git a/environments/production/modules/stdlib/types/compat/string.pp b/environments/production/modules/stdlib/types/compat/string.pp new file mode 100644 index 0000000..b06255d --- /dev/null +++ b/environments/production/modules/stdlib/types/compat/string.pp @@ -0,0 +1,2 @@ +# Emulate the is_string and validate_string functions +type Stdlib::Compat::String = Optional[String] diff --git a/environments/production/modules/stdlib/types/httpsurl.pp b/environments/production/modules/stdlib/types/httpsurl.pp new file mode 100644 index 0000000..36fd30f --- /dev/null +++ b/environments/production/modules/stdlib/types/httpsurl.pp @@ -0,0 +1 @@ +type Stdlib::HTTPSUrl = Pattern[/^https:\/\//] diff --git a/environments/production/modules/stdlib/types/httpurl.pp b/environments/production/modules/stdlib/types/httpurl.pp new file mode 100644 index 0000000..0d93a95 --- /dev/null +++ b/environments/production/modules/stdlib/types/httpurl.pp @@ -0,0 +1 @@ +type Stdlib::HTTPUrl = Pattern[/^https?:\/\//] diff --git a/environments/production/modules/stdlib/types/unixpath.pp b/environments/production/modules/stdlib/types/unixpath.pp new file mode 100644 index 0000000..ec3bf7d --- /dev/null +++ b/environments/production/modules/stdlib/types/unixpath.pp @@ -0,0 +1,2 @@ +# this regex rejects any path component that is a / or a NUL +type Stdlib::Unixpath = Pattern[/^\/([^\/\0]+\/*)+$/] diff --git a/environments/production/modules/stdlib/types/windowspath.pp b/environments/production/modules/stdlib/types/windowspath.pp new file mode 100644 index 0000000..bc1ee9c --- /dev/null +++ b/environments/production/modules/stdlib/types/windowspath.pp @@ -0,0 +1 @@ +type Stdlib::Windowspath = Pattern[/^(([a-zA-Z]:[\\\/])|([\\\/][\\\/][^\\\/]+[\\\/][^\\\/]+)|([\\\/][\\\/]\?[\\\/][^\\\/]+))/] diff --git a/manuals/Installing_puppetdb_and_puppetexplorer_as_modules.txt b/manuals/Installing_puppetdb_and_puppetexplorer_as_modules.txt new file mode 100644 index 0000000..3821507 --- /dev/null +++ b/manuals/Installing_puppetdb_and_puppetexplorer_as_modules.txt @@ -0,0 +1,60 @@ +# Installing puppetdb and puppetexplorer as modules: +puppet module install puppetlabs-puppetdb --version 5.1.2 +puppet module install spotify-puppetexplorer --version 1.1.1 +puppet module install puppetlabs-apache --version 1.11.0 + + +#for external access don't forget to add rules to the firewall (note, puppetexplorer & apache modules ignore firewall.d in Centos7 and enable iptables): +#/etc/sysconfig/iptables +iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT + +service iptables restart + +#modules add all necessary rules to selinux. By the way, check selinux +sestatus + + + + +#Check all configurations using documentation: +#https://forge.puppet.com/puppetlabs/puppetdb/readme +#https://forge.puppet.com/spotify/puppetexplorer/readme +# +# +# /etc/puppetlabs/puppet/puppetdb.conf +# +#[main] +#server_urls = https://master.kuzniatsou.local:8081/ +#soft_write_failure = false +# +# +# +# /etc/puppetlabs/puppet/puppet.conf +# +#[master] +#vardir = /opt/puppetlabs/server/data/puppetserver +#logdir = /var/log/puppetlabs/puppetserver +#rundir = /var/run/puppetlabs/puppetserver +#pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid +#codedir = /etc/puppetlabs/code +#storeconfigs = true +#storeconfigs_backend = puppetdb +#reports = store,puppetdb +# +# +# +#/etc/puppetlabs/code/environments/production/manifests/site.pp +# +#node master.kuzniatsou.local { +# class { 'puppetdb': } +# class { 'puppetdb::master::config': } +# class { 'puppetexplorer': } +# class {'::puppetexplorer': +# vhost_options => { +# rewrites => [ { rewrite_rule => ['^/api/metrics/v1/mbeans/puppetlabs.puppetdb.query.population:type=default,name=(.*)$ https://%{HTTP_HOST}/api/metrics/v1/mbeans/puppetlabs.puppetdb.population:name=$1 [R=301,L]'] } ] } +# } +#} +# +#Last section is a rewrite rule for apache. Due to population metric names changed in PuppetDB 4.x (https://docs.puppet.com/puppetdb/4.0/api/metrics/v1/changes-from-puppetdb-v3.html#population-metrics) you can use this workaround +#Additional information about this issue: https://github.com/dalen/puppetexplorer/issues/49 + diff --git a/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt b/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt new file mode 100644 index 0000000..69da2b4 --- /dev/null +++ b/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt @@ -0,0 +1,184 @@ +####Installing and configuring Postgresql>=9.4. Last version here https://yum.postgresql.org/repopackages.php +rpm -Uvh https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm +yum install postgresql96-server postgresql96-contrib + +#Configuring Postgresql according to a manual https://docs.puppet.com/puppetdb/4.4/configure.html#using-postgresql +systmctl postgresql-9.6 initdb +systemctl postgresql-9.6 initdb +/usr/pgsql-9.6/bin/postgresql96-setup initdb +service postgresql-9.6 start +chkconfig postgresql-9.6 on + +#Creating a user and database as follows: +sudo -u postgres sh +createuser -DRSP puppetdb +createdb -E UTF8 -O puppetdb puppetdb +exit + + +#Installing the RegExp-optimized index extension pg_trgm. +sudo -u postgres sh +psql puppetdb -c 'create extension pg_trgm' +exit + + +#Modifing the pg_hba.conf file to allow for MD5 authentication +vi /var/lib/pgsql/9.6/data/pg_hba.conf + +## TYPE DATABASE USER CIDR-ADDRESS METHOD +#local all all md5 +#host all all 127.0.0.1/32 md5 +#host all all ::1/128 md5 + +#Restart Postgresql +service postgresql-9.6 restart + + +#Configuring PuppetDB to use this database, put the following in the [database] section: +vi /etc/puppetlabs/puppetdb/conf.d/database.ini +#subname = //:/ +#username = +#password = + +puppet resource service puppetdb ensure=running enable=true + + + +####Connecting Puppet master to PuppetDB (full documentation here: https://docs.puppet.com/puppetdb/4.4/connect_puppet_master.html) +#Enable the Puppet Collection repo (if not yet) and then install the puppetdb-termini package +puppet resource package puppetdb-termini ensure=latest + +vi /etc/puppetlabs/puppet/puppetdb.conf + +#[main] +#server_urls = https://master.kuzniatsou.local:8081/ +#soft_write_failure = false + + +vi /etc/puppetlabs/puppet/puppet.conf + +#[master] +#vardir = /opt/puppetlabs/server/data/puppetserver +#logdir = /var/log/puppetlabs/puppetserver +#rundir = /var/run/puppetlabs/puppetserver +#pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid +#codedir = /etc/puppetlabs/code +#storeconfigs = true +#storeconfigs_backend = puppetdb +#reports = store,puppetdb + +vi puppet master --configprint route_file + +#master: +# facts: +# terminus: puppetdb +# cache: yaml + +#Ensure proper ownership of the config files +chown -R puppet:puppet `puppet config print confdir` + + +#Checking: +#Restart Puppet master +systemctl restart puppetserver +systemctl restart puppet + + +Your Puppet master is now using PuppetDB to store and retrieve catalogs, facts, and exported resources. You can test your setup by triggering a Puppet agent run on an arbitrary node, then logging into your PuppetDB server and viewing the /var/log/puppetlabs/puppetdb/puppetdb.log file, which will include calls to the “replace facts”, “replace catalog”, and “store report” commands +puppet agent -tv --debug +less /var/log/puppetlabs/puppetdb/puppetdb.log + + +######################################################################## + +#Installing puppetexplorer +#Download zip or clone from git +wget https://github.com/dalen/puppetexplorer/archive/master.zip $$ unzip master.zip $$ mv puppetexplorer-master puppetexplorer + + +#Installing additional packaes. +cd puppetexplorer/ +yum install npm +npm install + + +#Install all necessary dependencies +npm install -g grunt-cli +npm install -g grunt-rpm +npm install grunt@~0.4.1 +npm install eslint@^2.9.0 +#and so on +npm -g list grunt + +#Building an application. All +grunt --force + +#You will get 'dist' directoru with application. Move it to /usr/share/puppetexplorer directory, for example. + +#It is possible to use web server provided by puppetexplorer: +grunt serve --puppetdb=http://master1.kuzniatsou.local:8081/ + +#However, using Apache, for instance, is preferable +yum install httpd + +#Creating virthost: +vi /etc/httpd/conf.d/master1.kuzniatsou.local.conf + + +############# + + ServerName master1.kuzniatsou.local + ## Vhost docroot + DocumentRoot "/usr/share/puppetexplorer/dist" + ## Directories, there should at least be a declaration for /usr/share/puppetexplorer + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Require all granted + + + ## Logging + ErrorLog "/var/log/httpd/master.kuzniatsou.local_error_ssl.log" + ServerSignature Off + CustomLog "/var/log/httpd/master.kuzniatsou.local_access_ssl.log" combined + + ProxyRequests Off + ProxyPreserveHost Off + ProxyPass /api/pdb/query http://localhost:8080/pdb/query + ProxyPassReverse /api/pdb/query http://localhost:8080/pdb/query + ProxyPass /api/pdb/meta http://localhost:8080/pdb/meta + ProxyPassReverse /api/pdb/meta http://localhost:8080/pdb/meta + ProxyPass /api/metrics http://localhost:8080/metrics + ProxyPassReverse /api/metrics http://localhost:8080/metrics + + RewriteEngine On + RewriteRule ^/api/metrics/v1/mbeans/puppetlabs.puppetdb.query.population:type=default,name=(.*)$ http://%{HTTP_HOST}/api/metrics/v1/mbeans/puppetlabs.puppetdb.population:name=$1 [R=301,L] + + ## SSL directives + # SSLEngine on + #SSLCertificateFile "/etc/pki/tls/certs/localhost.crt" + #SSLCertificateKeyFile "/etc/pki/tls/private/localhost.key" + #SSLCACertificatePath "/etc/pki/tls/certs" + + # SSL Proxy directives + # SSLProxyEngine On + +########### + +#For using SSL you should include additional modules to Apache and generate certificates. +#Virt host has a rewrite rule for apache. Due to population metric names changed in PuppetDB 4.x (https://docs.puppet.com/puppetdb/4.0/api/metrics/v1/changes-from-puppetdb-v3.html#population-metrics) you can use this workaround: +#Additional information about this issue: https://github.com/dalen/puppetexplorer/issues/49 + + +systemctl restart httpd + + +#For external access don't forget to add rules to the firewall (note, puppetexplorer & apache modules ignore firewall.d in Centos7 and enable iptables): +vi /etc/sysconfig/iptables +iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT + +service iptables restart + +#modules add all necessary rules to selinux. By the way, check selinux +sestatus diff --git a/puppet.conf b/puppet.conf new file mode 100644 index 0000000..fc91c74 --- /dev/null +++ b/puppet.conf @@ -0,0 +1,15 @@ +# This file can be used to override the default puppet settings. +# See the following links for more details on what settings are available: +# - https://docs.puppetlabs.com/puppet/latest/reference/config_important_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_about_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_file_main.html +# - https://docs.puppetlabs.com/puppet/latest/reference/configuration.html +[master] +vardir = /opt/puppetlabs/server/data/puppetserver +logdir = /var/log/puppetlabs/puppetserver +rundir = /var/run/puppetlabs/puppetserver +pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid +codedir = /etc/puppetlabs/code +storeconfigs = true +storeconfigs_backend = puppetdb +reports = store,puppetdb diff --git a/puppetdb.conf b/puppetdb.conf new file mode 100644 index 0000000..2c8643e --- /dev/null +++ b/puppetdb.conf @@ -0,0 +1,4 @@ +[main] +server_urls = https://master1.kuzniatsou.local:8081/ +soft_write_failure = false + diff --git a/screens/master_main.png b/screens/master_main.png new file mode 100644 index 0000000000000000000000000000000000000000..c95602ffe683d31ee267543e44c63356ff3eb38f GIT binary patch literal 132011 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 z zy1LfyimF8sAG=2{jca=KU)9XgM9iw?o$c};-130th?BE;aR)C11b_1yY?UoyJj=&<5nqn_lAp@uxSc*ig>uW?T@{5pBupmiSQIZ>Fc*5TAgKe_j#Grt#@h^p)ek`%l_x3)0(BO~G^}gD0 zDTvE>ZL%r=6&4dr=|s5L8?6;&Cw)k9?_wu=M38f8>T`Smbkkm`bj_*%d^zngd$T-) zOt>RmXyJC2#oCV_2{`R^<})^^be8~iJ1YF9RuJw(N+<83+~}P+aZrBpv?j4Xc|fO4Zy*>sGgmWcpk2x${}y z7Mzaao9%!$!;QO*IH-qC2IYO&unsOR`=jT2LzqlVdtpF9kYUXYDYtOl>`S0~bZcJk2PV!`~5K# z0RrlaAdvZk$Z#c|MXh0lSl0K3syufsa=R~qI*od&SN<&*2FMim;NO%n5G|#N6yW0nxJOq4L0YwhYyMn3Wy z27z?Qk$V1%-HTY5wC}~)#PP8=I7s_oEs+6OvUEBr=S!e;j=kJ4CMjRvTvs3ZryS*&)MKmFm7oug(mT4*#jHn!Y%V&VB5 z1oC@#+;kb_SJ+c0RE|v?hJ^B@*4elLn-#pfd`-~)$MFWHO8cV5ek>Q57aMtv8wJzh zV%jv`iojn52_{Ws01Ca+S^fp*v9aRW_FKvwqt>w;jZg7@csJ_dL{*a^H0<5B}FRM zhYv}><`o131ebPaFy+;q9ZLtg-yL3G2e1$<6SMUAesA9IYaph`#NpPPuO|BwYHWUA zdvl{Fxd>TVNdtMv!oq@0_>%B2sI0u4&kn%#zlT|Zu;TjqI`GXgML?>b+4JYsl+8dY zwOH}bMgSj7W&~Wq=9q&ifxTB0P&}NoFZAZhZUv{zQ$PtcJ!H zPx&WJ!^3&XVj?0Mg&J-DdmPFbcwOo>Hdahk=91*^Ti1SOfJWDsyd7` zxcR?y2gWX^H2qP=ySIoA*#7wz8z9XQ(-lt5In$uuuZOJfo#y#HkEYu`WnP}hQ;R+!Ey`fw(O8!#^^0DpHf8y{F zGXE(L;i9tv`2L@x%=+t}uOFJS{zrFCx@uz@rctP*AYjb)3kz$l&%+cfQKvaKbwBIb zpiTi9PV;$0uAub;Q#Is1wJl2x;5I~x4F(C6H%ni5P_a}e%cmm?v~0sS*t?0 zdQ%a){eB5O?)tC*3#(X+-+?^!;abr6QW20hS%<`IPXbRHq9j~0OV&( zJ?8$8@;Yu!_+R4u=kT-tvnk+zDAhh`*Z=1IeW8o$vG}jrGSC~%2$Hb*>gmAyq;a1r;;D#JSRM2366`u`mM zJ_=Yh<}c*qc_t5VeT!)Acpgo?Dn2kc)1s&!xQeizRojTeADxev==<+2DMrc|R@$Gu zCAvb*YXrfMd|{8-nre7#a`qLd5|V_Sb{-;8=KXk@V;rhbDjs^j^L&Zb|tqSvk?}6`W zIJFG$8iWh~;$k?xfb};A%+md|^xotu=aqrCgM)*>t7#$mqifT3UJMco^fz~hCMI%R zvRQyylgje)S5?>M+as^~Q}e^7zFMH;CA^)n6B9KKm_tCx4~UyyS$kDw2gvC2_sEWD zCg>i^5TYzETA2Vy;o-HUx0x$5+49cLA4?`D8hZ|Q3r$2SM-J#2{}IoRh$lu0)E|oj ziO!Eh-LdEqx`R$$S!OI-JQ}T?uafE4R_`MjL|Ns8J+&PniIp2g%2&BAb{p3pAEErA zz-|^xYik@qBL1f%=foG;95?n`wT&UHZAj-H=yF^H|} zXUizk(rSZSFV3M`Cs_YOt_)yFEzohmMonXb*|OoZys8;bo`PEsaN{!3cM{Pv7ZJRt zTE!|m(b9dum(v+G!~G5p8$pyP6h8fmM1#wGZM=5mP>x(Yukq~mY@l3D#j_A4NT{Hl zR*7L%E$sX~Tpw9pQE}>V)Q6`yOBOhhnC7`FeSx26jqV3_6w6bZuJu?0_Pk(_^!@5x ze)IA*8U;Rs_c*>OL8RXK!FYC`vB-FdL&%i_dMPsRTK|r*o6PbwoyJd`#;;$0u{vZ? zs8O)C?3JsFSTc^fty!#d)y~eYr97tu+XR?I023tsBfNA~f8ZW@2_KDMIJNS>LM6Or z?Dh3Ai)gJ04CWd*N!V!+AHle{)UVrMn_Brs51#fI3j6*pIEYd)Nnm53d;=(D6Ly-6 zlMc89ejd3d=o>KDvbCQ0nOWWjjhUF>Hm(%YE-`?FF7@i|uTA@J)Q(Q#Y?V``R%bss zeLrgh%qvLf{d9jJ)7Alm)%mxu*We&vr;2hvWF`C-Ig_XXJZU^(&@7k|ph%5^(T=Ed z8XB6Vxw+4dv^Q_wT6Jo_P8tecwO6S+1o^ab|w!?6c3apZ)B; zePF^oJn9V{Ku{IuSy|)K9sNok0L&`h&(tDdk$?oQFOJpGslKk>j4%&^siszQw_BbU z&jFi)`PO}WDv8@_j84oS-yhozi`o(AAvG59>s)SeCco0EDqw-{YDn8^%Lv1$`1e*; zKFfe61ojpV?XxUAyu5({0nJ?>pKi?_5_Zim9Ge0lis=tgD9Ap)y+(p>?cNZ(|1q9a z>&Mj?DN7T$hJmsmV9ES0BB5bqEV%G_yb48LfUC&tFymJdca%N=ivt+*I>b?5R_r@8K$)xA5s4 zrCWhrE*)nh!m(IO~FYN$1}33?!@(D{eDX31-5UMB6@7ir=`)FJ@9u6YQ05I^@D_6Xha zz{JIhl6c`Ze~d|ew!ffLue>cu2%QijqM)!cd?}T6G>|0W;Snenn#f^3{^IRhyPa`F zwdKrsxhVX9zuNx3TWgyhx8-nm_k5+~2Yqu(^Z>>TWZ~V{iXgQB0dfRUmDQXsLrk$o zgH(|!oNIp$DxRmzWj?NssZd*L@*CohM{yJyMUZ!s}|6wXqlkCEEDhYk4-E+$fq&FEL*{7%+TqdvO55-k68?aL#` z_ZpiFwtVc)af4qLRZVa|e}4)g&yQ-$DPR-ynt!S~1O!4@)V}+akZ`e8uRVGccXyM-78Gd3FsRH`S?OhVn>?eW zLPLw)t{D1(ODW)Zd6d-_fM-3PTc%9YqI{4Q1{+I3PB`4cBxCa$lOlaqJ%_T-a!HrBS*)>Q<3 zkoV|LM_OzXIOx#Ol=77G9X4-pDTJQlAAojkqa7Vpu7?DBb56PHWs1gu#!TQ!D^<|B z|G<1Q_w;zELReD(faZ=hjzc}4TVAWHpVQOo)P5;Ca*T`}AhaGz!=U6_<2N0Su-5S~ zbo~zJE?g+p*J%p~ZBAnoY-7Ygd)~J*Stb7Gp1`Rs-FLe$Ob@UrAdoPx6l>1*%~X@(ar=TUbrVrK0&w6 zo@h_$KKi35#x>xYoiBT{fa4!Xt1%trUZoOrH@%>A|Dg51U|`s3Ks47m-{zmGSy;fAmVSX>>?U$mrXxG>x2tPM zyOp0Zf`bdr$k^$58franJ^|XsU*tSS(!kHp&vPV4+|$#uss1$Z(X(P5T2?wb_swDo zfO(V)CdRS{a`j5ycLqq@Ii>KHuAI1-n2CH9I2(UN$oWGT9JGVwar4t*ec%0s2KdSf zn`8IEY!!TVdRjzG42irRae_~^cXid6j+nPzNG4DiUpcA~vT6W*eBDt3g`AlC(OQ{2 zY!QM9WhY_dCq}z4OsM8Mw1H9tg@>OXbf4dzk2#$ylvVW2lpwwUs^Q^XFNEmBw?GgE za44n68PXlGxS(BU&#cjyZnv7v#l;2rK40emWa_tF&xEC4MA3@l>NGfWRepyW*z`N0Ag;haVqb)f)fO6a~Z0)Ea0@WmZ$W} zAHOR+Wzx%Q<7`jMqNba$iyFdwrfA0Uvci<6MxU@~x8o%i^* zTI2a`s!I^aMz>>Yb*QMve=4_9nd`GXBFM@17`M%0BYkSVW|d|edT1r~KikCgx)sh7 z8v|rYKFdxk?RGxM)2?6dKe(T570t!}{OPzAfmo&uOclhA=vnIsCP550D=pNYIzBkt zpR2H4ZUi_KbaT-vcZ`Ihnr0WBT4`IyQk0-BKC+4QWT}qW)Zl#bc$^J6nNLyh+MjfW z!jV_kCEPYNDapw@6Bffz*gG<^qyjb6Otn>?AgaO6&W@7uQnS)@Cd+|)0ZVgM40>fQ z^LU6xtJ=EWVN*^~@iXwsb2aecbkPFG>!|$$ENWmw$otpc{O&wU8USCN9USE9LG{nA z1$)rZ-eG0y$R>9QhU2e*byQHGx2e%NsYoZAv{@|mb_gS{mL>IJ@#FD9S{@};Sa2j_ z`y`Vgs0Dbp?^_3EBD`vA zce-~k75C|u!$}P-TY@rOw7;LjikN4CuNRZjs)@TP<3Gy3kQZwZD_mGfA z_dP0|e|%6L`uh3+&Q@7WjaW50@8vf%T*nT~o$oICoOB;9w>3Y3XxG{z!9581{`*6q zd>Ycg7ahkRf4w&*N$9p~d(3Bik*@{SjOtanYf5vjl#QwfYPj`+^ZmoU;pPm0yt#df z_cJPW=FUJ+0%|QZYrI48AsHh~>OAsAs@xWn0!4Fi_t8Eja9K_t!_J{RSom9ZBidIB^%n)odG$7kKRW#9l!Er2?cXCI z-x*QbsnAc>;NkGDC~uvnnqgt7TccS?_$w`6y+E&_9+z_{0tTo!-)3=_{b{e^bJI;Z z9e;d4{?;E28QpJsW9%9Z-tBM{CMP!tpR!skck7yD=2tDz+}z#00XBw1#>?y{==ICDJos)z%BnOD%tJ$hbzWL6@r! zfnlJ--12e>_@I+w55?^ZJ3=C?j<5HxDZyV{BcKb-UVS4Yb{D0QbWVV40=(_^!|?C; z5FS3hY?YQj)aMOR%gb~VHy4K)23uQE2bAQ&DT9DL|t-X!M9TdC`Xcp}X1llq(SQ@_kiw`5IhLBSk%bn{sDETF2v z5=zH1z4WtnaX}f6y6`$|jPDD%4iAI-0U!;qwl81{a}8&}H&))3mz6zx``NIreBB>( zS=>doEH^o9#^R^TrPTXllT2FEYgVf3r6IBWSFTPi0s6$fruuiR|SN6i$xaU z;9dO#0`})>O!}uX_?*paoaQ#xV1LX;GxP9Qc6o&tP)}L4d072Sr?-S*Ve|@(enmxA zN6Zhfh@U`2n%$zUpX!A_n*wjNMpjfH_>EmiaEPom|$<-8I2Tjc7Jso}!W%ksl6>Pjz)FD-A zKW5)ERPE~Ktrk>qs_0TO}Mu%UKZSWPqCR}W$_37)EI2vizSbMBoBve^& zWvcL^snd;F>*vJqyX=F5jra7;763?_gK7LOgRC?(o)h^*CWFbT{3c(|9J0hhHGxC= z@%=kUGPrmAh|vWN!!HhLLByTNX`V$GA;aSLu|Opt*bLmU$SoUj04>O#Jv&VEPmd}J zp!VAuX`0#|%duW;r1r~I>CPiMAjR@TZO;UV3QNes3x@VxwHRgXtHBYc4;{Q#1tWymaI(&T2mn# zj77~w1%ffD(=#$)&0a|~j|+{~4yrBYkms;)baZs*y~#ZLEC!W))gsM_f-MtKoElsJ z*S$vDH31dhR}cV*4+XtYUgzDK4r$^=C_>m{a7b7NC3$VxBpwknNJ1FYt%Z_chGgX56RuG==$vF^lYyHhrw09@!N z7D6+WrppY9ky@`X`3Hh2KSaZpAXB457tLw2INCeweszKXCz_1tRP6fY&um#CVaM+C zT@5HO-bru%Y=sQEXGG_Rt5D!O`ayR=WTPkux+krK-A^QTL2cV&+%&n#W5s^PWX($~BhjK%f=Oq?6 zQ@|6VE{+eJ2d*%n8c;3RSl5_7o-9i8IM1%b61sb) z7W*f+g}H%&;G|6-!cK^JoUStQfyLBYhwAL9CiMgnEw0LD3kmNl*XmRb&2Q#gM#Ki|H#9dL#4{D!LyztU(3@^~d$l~=PBqX2yUXbL@<@HWzxe&6(!ACLP9sG?KMVju_-L9mG7+EAtG z3c5SX+A%gJRyF$LKYqN;golF`+gVvf67YzUE=I})vB>E$SQy6&^YR9~?J>#V?NQ$l zV^NFJ6dXj^yfJof}N_b9&U}?&r-~rQ_oj6qxJ*v+hVHN+KPA>n{oh(r8kb%rvod7l-rt=ib@!uV+6_HBD5u?(LAL%KzNFa zHa$CB{|K!xspK{f4h=m$T@co9<*CwKsO;HPVNjchDf##t^b&r)v<wI8(4R3{nyce!GKLp@v*O}M7Ei>v)=HoOl?iG`(XKx3t z(0idt30!|?UMmu=;W=}7&%G^ypk;e`Btqw$orxRNBHzYfUTV!I94OT2_0{oVm@?x_V_WliDs)j zI1eWOJ~%SJ2B zD)LndC8%qEI;HUZaq3g>o3G<4S0)ybw@ zwdJcYf`rWSmT5tgfmyM3ohp-FmGeR(K=I-te*AoV6L~tDfR7(cV29uMJ8)a;TFjKk zKv9Bw!Vke$n4P^^Fv$d6nL+X)OPYjPt2$Hcl$3=fd%+*TitwfFCg}tUaEdiVm!qE5 zJud+_&)M+?i-5}@Vi-h3X+ohPA+}CV!C)JMg3iv*eNMLYg9u(m$E9)D**AM$hEbse zfp?^dR6frisZc{?h~rTSdA@dgOF|My)Tf33;{?|72)}+E^22vKUIUH3y+J+o-tKNl z$e!TF_-sMA(wN+IXfiz+`!*L_sv}r|993on1%EZH!G-&+f<~eGVHgG9-EDKA+xe)( z*B7h@+!?Xxw9%i}viTMCRv#D`=qnH#qHF@-g^j@*?SeysgH-k^OQ@yR&VWVt%40i< z2plbTiw{7_z((*HX6TFw5>rHz)wl_x))L))Juo@7$%{uG= zitGAn-v-vanp@Io2-0Amwil1F^k^SoVCXg5_W%P*U^5z+{cnK7v+f1Kq0yI&iCmWg zfYt1qlufYjV&51CYfRT>9i6BN2;cjV4{_yRo2$_!dCt}eKRP2_OUE-w0`=wcZ` zYs}6+e|{SeXnqI`DJ?H&6aR0Jji-CO(tR``p$b?lkYL3B(3<`S?N_u$P3e8Kmh|{@ zV&}i#0hj?(*#9p^LX9*lZB}d!nK%P234rv6@#}wod}GXK=~v6I&7PVN^0(pOWH@rCV7$XS@$kcu=qs!>)>8e|Y=E(2>6NaKz8E0d4&9j=>TbUC zl`Z1pcO;$VbFf$m6p+a%Ppi)f+H`)dr1%FHJ<(=L*|iM~*wmWUre%gR_H8kbsMy*U z|11tp%H72d@DeDJE87el)gMF*$wF|oY?*Cr5*FpxYKt>7m%e;|%`M@tQf&q;SS~Zd z-D>6FUqpvVFm1P17H^LtSLn2i4mEy-v#*FlIN2sRO+!Ge%+%1zdX3cWP z_EPpjjX_r$6+DF`WO!MvF;uak?QG?>Qyftl&) zMz8C$%j4%HBqU^H7Gz{_KBpba>B=(I(8Ow9`}myTyGK4aG}N>Qf{YiP?Vx`x6AMUg z4V#Wj=C@C~hCVo>pt@Wd%E!=(b;an;_r2fpRIAzsK3efND9#Z60FO-I#jexz7i!6{(>VI%J|9hl`?nxT2i$|>xj7z#VBh3^t?dt>r zos7jJ!<_fD@W{B*&7y~Jeu7C7)A}~j z&iikaZo|_A5_sH>&IXb|``-jm0c@%<#|OM~fZ^nJV1hAY4}A=XTF99GqmDN$)>q#c zM@8wxpGI68%H{mih(So40)$u#%^#9f7^h-iD=0Q}z)*|7{@l8t>W3yK2F3#>7*uvb zZCDrGxg1j1E@GH+tS+M&Ws8niFLI>t_I!KQs=Yp`xMNVyfxO&}(OJL#2l4R&HQ@?_ zp1bE7GRK$;>^gp|8w4=O8NUbJ>Dg+RoR)@>uaUl+)ji{=`1q$cD;=P}dm^`$RM=Cr za()0)RVODIV@qO@4fr$J17vb30%iJbvHdtQ(nYFid`^IDaG#u^{Q^mf~(GKcaDS1{rmL$?xuZWq{C9bE>TOutZX^A)7tc;iDl)1dR?)%|~QwmcG z*=JgXkuNkj4ehFD?F&Padp;%;eNa^5S102CXDwaS!g)}0A7$1lW@I&_rN^4 z9X58K41tCoRASJ2a9aS#Ljsy{|(^r)>Wd3>sh06w354t{uP4taQlZh znUz8G2bxGj=jStI7(T>?78V6wpR5d+qopXw%TH=3lvb-AVo&|jZ9)ek$BYf&Rmo?e zFmlfOXl$Z@K$$AhQ_68p0mK%#tm%3A`1WBgZz?jr_|H~*1?QMVB64J=u6J)99D|6U z%m$YF$TIRiWw-XTSUMpWijxZFNc2vu zMl;Pc8{o~t-bJzDXJ60w{%rNR_{QyrcwER}>T8wTHotAMk_PGQ@rS*?F12Xn^d{Ro z=Tu(neuC)1@9p;#upMD>f=$1D5|gdF0)JTB^&+HxqtY|M&i7HfrZ+1 zF}7QYgtJ#9XJhfQ>ooH9N%-Y|NJa}&p``h|u?`yu9Ed5A?uSwSKB!xeoN+qcnFz$W zWfP6d<3xnt}qH48^m)z(}iqNkb^=YuS)<_l~26b1FD!f7Ok7D zkq@t4x;l|( z+(~*@sZl?^6XUQ4>-~-4g-3*Cc41-goyJUcXYQJ&j`i%0Y z#0x-3iq`5@!SQ{tmpnT6nS z8zuTPS@=gu3ZI?)(b=hZHC0U;Y~YtWP3LQS09$Q5lv?%a&&m_j=b;mgAYdS86tkA< zP`8x|}(J2@F75(iNT*@k(Ad@{Hb8b2C|!g!NX?sCUgw z$wawCm$ryB3Qrm&6>2OO9IwXfDg#8}fb|yg^1vhG`;7Y91a591;3Q*nod)|;q6gjB z+T!?yYRQ&OvRbHiLm`_QocAY?PW6s^)6HI8U_-VjrYq?<-Jkg^rru<7_%z=Girga2 zDmks7aQPC|qFKkC@s4=6^y2Nx&NoF5?dwe%-5EKIU2&AX0XoK7XY?M)F#))GkayMPsw4Q)*<#% zuexZ)K1!X$zJlE`n>h164AvQX7yH*$W@2@#jP~5cly8jOj`9`9Y5jl;4u2v%yB8K} zA(Gmh>2|FC{jE51S5 z-rk<4fwb+;OvZVE+V9oQ@WZ40W-EAcnV!6Qo%!V@_GYEg;6(~0mt*^tQxXLQ1vntd z0=xg*V#p@24J4u}Kn6{E~A%O|3tm-~kqfwQD1 z&e{n4ZPFsuB6m<2bc=EBj}M~j>hf~FRTnW#yX!&b1R34mkgUop>!p@Sr#I3RH+kDQ zAf4fJz3`$=yV`jmz)Z=<_2$A3xSXdv#UiAErkW~7DQchVnbrVhLPEcoJvz(%py?1^ z#K64WTz=Bb3~c+U&HVJk^Bcs~6>^j5Excb!B>!QCbH>+-_eF5-VlV#7Ya?kjfHdV& zxI`Ni5cIiU9ptFn!Gxw9d6akWPqT@FDTxAf>h^ObD9{qJXzYx(UCvN&NWwgR$$QZNvUBAklL_*;-DXIR=L0j}L64 z_(K;Lh>s=CQ2KRFo5Es&_t`kBtI9j`OSHn~jKS1U^B_-LN4bw{oaF7VG)@At>CLh;ir zYA6ArgAdu-a|Jn7MZCyj1(31@oP+kwbqPU-^hJu`SP85}DM#*PXXd(hy>FsGEv|3f zti$-uAZ^e0bu>1Y;pbP(a`$^&2UViO1RQ%nm0bom{du9QJ2K>w~TB0SGf3UD)9h73Y3I ztD~f`Pogux!Q|!T4ZimPkWR#`S!ln`0JixxIQugdCJZWt zyDKZCj~}PHe*%<{rK{rcgc>ag*J=0V&?Mq@l+BTV=DlfYkcj9`^y91uH8n zfaH|QMI>2ENWn?S7TS4)VbIU)G zF(D&k?Av9q&#lvDcS%5x;nCS1|9Gx0`-?RrK(D!jb59opgut?1LU#VQ6Hk38qx&> zZk!ey-JIs;s^AgzAu=rTQc~`SQm50*hr z^T{J(h~xIWQ&(6Kt8Qbh=elA}SPD-2ov{%O{=M;ap}5gKhO=Yz2DCEQwFGA5>1HZ1 zMtVVc8r>FS7s7g)B^DI-56OBC7#YDl4#XUw8oo%*)*xJWPI~H&z3G**3!SC!46Ea! z;=L96!72O=9v)s(&M)_&nV(WwcWKzB>T2g6_>@)~{NN3>$j)hsY$7@R6Z^cAg?g%4 zraGxC0tm--W5=jbFrdzMKq7-u&?A^*Pz_L8pIjBeaq`?dT>{}RU39|FE(!0&xpJcd zpb$CV9y=e;i>3_-=a`pmdez@4^RJ_is==Tu&d^}x39gN1%-r;}DTdUw1z^nl@WIvZ zeR5;rcYuo9j+bM2|GR<4KNZa`A7x^y_%~pV@Wo~vB9bJe~CBM>H z`qL6SP_lw5m{;M89*|$0Vqyvk|Rea2UgX>ixp86DlU8fw!;+#->A5^=|qx!l~>9v&Xl?eM7*&`)n(d%^L6 z4C!%DbmvqF?9fY>c*YF*ya1qCU|as|q-d}T0z3!_ZA@JvtAz5@bjKs21r5#L zNXsGK0Em!(ZUvZ=mKT6yzNrnm#klYbXl{7N#yBJ;Lqtx_ac^J{fyCvpE-i6jxJQe9|h zXoE*8zl)6p%8mehjYx_4`FUcdWWX=X6#G%kveB+MC+caYp*?B<8 zYu{Z+h||`9miB^hk}g)_1)x}UnvB=ed~P3;lDZ$R=mAxm`CN7i7`qc3jylYtl1mdJ zA|uPtQC_4Alx;)D0V+hGoO(<}ba;() zwz{5p3;vWjXRTQ%sI5nJpu|WR!>apWaOTD3xt^ljK$9LqIOYju){h|U8ONuD-=d2kHDEz0h)zx(A& z&iVerLbX+Jb>n{?tB8l6AH;pTK(ukX!-)@6MZ>0DgI0h-?n_|L`8plKTUs&x$HtIE zKrr|TqpIg(BQMYx4W&LH7jS*2)@-XkWYFd90nYbbnZ~~B`K;u5t(41+Qum443Yc{W zF1bg_#YWU61C>b69$2;_0!XhV8Iey^wif`LYm1-!>)7(Jh!}Rqa$u`n;gwM1Dvh9J z_P0%Edvn(}Zc9K~P0XzOv0m8>4==n@?mbSjgC>2UR1Cuc5Ej@#ujzmyu80(9qkppM z=`Y^!%BKn)LJ?5V&*XmF=bbc=NX-WNHH*o@e2{^joK(%UYc~I+7(E-f_Vx*uC_rSa z8uhtA)L(Te5?yb$Ci3pp0=upfBS-m>wN8W0Lj5HuBKY&?J)9IDXY7Xe_X8`f#=N&jML{}G zqtQ?SWMKjxJ!A2Et&@KOR2@1{eXBX=qHNTgi5z+167vZiZSBn=J;&2oVelUS!SG$P zh(#qoB=2xNZRyzXD2nV9UEtQ+U%E>S*NJgqk4phF(&eMLisj8hF`+dVC4t+H;xbj> zD;T`uW%M^<>n7bCRK|$Im{n{2CrTCVMml=Q<(+uzh91P1xEod12c6e`*8KKO)+mfN zW4@|F=8CG3+;Irzd9zi_@5e44GWDmIrQAN+tZ%a8zDne)oHAT#jQn%o>rR;vBV~f* z=$o8$?j8oX9DC`{L@I5FD#r+n{WP6NsZ1Mx?11N`)I!x5tbzgA5NM{t+HXR;W`nvT|#pnBo_q zC%xOhg#|!=6M)toM2s^t>nFu3bGDY9AuCCJz{LU1;jNG3CUEWM)3Job|m4~wMS`(&Eo!@%8SK1fs;kEatUm12N7Q{kJqU< zcPyy=v{`Dd;?8y~pTI4e|5pp(4~e{)oeN3acpNpUDcluQUVSP*2AJ4mN2S2;945MzNND`NoUZW?N9taYiaAS!okUIssYUx-O^OdX+^tJ(Q!!a;gbV{I(#yT9L4o& zr@uc0 zXF)UpC#jP$#yzxm*^v^Nigh&XWB--I8^&g~yQ+MK7>oad_Ad7M5U5LO;Yjd#X0J*c zDS_%*OzG+vZ~6yKxT3P4l2KuX#zRsfIEzMWhOsh-#zK^aMw%v17nbCPZKn2`=S%;zaCQSv+w9kj0#MXE}JlOse)_& z3UI7iyEG9R*nQvXwZc}}NvHn2v+L6^>9X@vw0d%HW3Iao0Iw~_Z_I>hYHA4FSu?Au zt>Hhkdq45fZcL}75968#eYV#w-{*|*;CXu`BpTOkgf6bQM8{TJIh8mS&T)Ne=S5#pB7NoRG&qeUA zHE2`G$vh2wx=hCXTf8QroB%=p{>aB4%<_VEwXN>h`^_B_1vLT1znVjz!6_I9<=Oj)0>B|MU+H~gKQO-V?o@-YH76ZCh5 zz!yh+>Gc`O-D(2^KE*MgJ%i89@J`S1PL7ZJ`3DMkT|J@ZkTUt1lT-8aCkNy;C+GS* z5p`A7Y%u+B0+OUr!PL|^*(l@rHHEB^TSQ#^9PHtULLhc9mWr}6IeBvEqfiYvTbl94 z>Z-+?MSSX$sR6RGvaZ3j20#2(K~PiA&cU(vL99Og+e*~SGdjBL!r#4pec#Uh&CYC8 z`uLHY&++N8tBQ(|`UR4NH5D5>cx!vRuE9-0S{gg?6=dtQKutVMgqVZ`hn%Oo&p~G> zHNTd=Ow`)cbefFZas3E~An4@KAfO_+Eic+cMa6@Yn_E8Ztx?yR4wwR7yVojcs?Xat z`q}yOXdg)8=jWekLajfbkoVQ2+XaJB%WdC02Ya=>Zy9PB=wvw58@sudmv2W$6`6~u z7j@z8Cm-o@jXGtHC8Oy;{ekCnslG~#isgcwfJ|~6jcUBZMQxU>?Lg(8(_%ex8A@%* z6Wz7bXzf`YC4s`f0DrMZlmh>9AMfrCIiCeDEX3+H zIoGA9e;$9WGnKWkzkD4X4eRah=Hul{N|dAC!dYP?CUF=Gs9D`DC7CUe1B3^xnPgv3 zAtgfO^K<&!$N7uZPaeMgwyGT|q2iKC?4OdMGUYr*F+VpXDj|kNtsM7L*KujSreDA> z;^N(Dss(W{-e}8Ucj?@`MT(W~yOWwi&+^jYgTN81r)dt+ScOeX@}PZOybEEHY{K~T zbhoFpxQDXs0hJ5m?OG2V$id@aVLf{I(BK;SR>-G=y>E4pS5#CKOi`7Smj~N9J~CP7 zf!Gg)*WjN+(`~Pf{^#(ZLI3?{U)tW@@$mRKE-FgS_26Hv_&HF1AS=5UG(GlKJe-7x zL{yAY1T!Ws&dsglnV=@(B=0!YVoH=!=;ER$156w^g?p{96A@4ffU4A6K-~j9N>QOD zi*7BS%_79d2in?tFZJP4s)~+Illkcn>JNdbyk3g~UT(vwI?tX_sSh0;A6uFIrU>C# zY0w|rER@-Oo+5XCq^qT;=flIs$8qw`&8?$TRtUH@A~)}D3D_3gg@;X3UEMW0jn(&0 zi);{cd2z9>m)C8VJk?_e9_2gQG;KqD!fNJ&{Td;ELA0cF8 zW5b|nRcBuVzO5~=!!5!#O^Yag&BccM$~ue(e`a*Fu>*$3|6L1vqa)ZvPL73ZGI)kB zP~*$o8#~<}Pv?1(%?S0_tz1)T>U}OOF<(`$)j2)aLJ{gQ7<7ds@j1<=HZ?$fP|YUt zzd}z}{`dyshg(|f%6(c|Tw1zCz;c+KoDwdZ&lyVY*hnyH5em6|^uh~*H33cY} z4o9`zkETz>3B*(a;eM|*P3~jpJW7|4d_ut^@0tAWRG5_`gB!1zh$T<3lxLR-@_hu- zH{)3vSg}6r5%~cr^qkjSVzBdm|B>v2tOxJQ1l81WeDFChNQM*Yv9LS$TQ+=g)rJCw9W{{RJLL zNlBBOEX?0_2B%%2V^9(wH6@4V&XD)-k=G~ZIr1rO)<-&}!|t1dXnr3xHO*C3H&TCj zfsMtahT$X`4<#fdTn4S;$}YNTYO(Ij)lfl92h4{aU~g`0Y|y=IdLi+UmVqI^sOb2p z!(rf`#D{oPJUl#IT`(QPiVGFRsMJ(2kG;bxF8v!#O}BG(n5L%hV@NigF$SJ!>10Py z@V^v7-x%It6RYb}Y}HMA91_6O(`kr^h~_ol;GPXe)5+02wP_9v!Zev}pL&#&Ry&9a z|8?6|K`rpu<+%yU0iIyAPZ=iXOi5|IvO-W@efICdH#>XB$e5T9P3}PT%EisCe?G6F z*$;%erV0xBQ19EiQ*=Ah8;*oLJ|VM~$WUcoGrx%Rf$?y;Id``?0`kotf41s+#Uvz& zsb8=!9QY2P`{4&kJ+nXVx?&|DR@7!|dVNH29J0Y$Wv&$~wS)5>Gg8#Q%vT$6NhBbY zy|wh99KR|s`X$Qip0|@BTfz|{n{Yai^fe=615X3xkC3PQs%dT?hhY@84!AK=1HHZT zWnLs_I)MolOc2Popy*{Ep*z(Bczt2n%83mE%GS2lvz#XRqM{?>+Ujjr#F%mBnjf{Bhq-0i4arWC6 zDEZ~6s5yKVzg3vX1BCvnNh+D&gc5?ujp-}5Nt_^aS?2uix1yG&raI$FZ1}G>@sAJ4 zEESr|%ORqo4{C_3zsw)-%Cz&0Z8&_1*YrwpXfr__SYpbzQ*_Uu=H11VmB-D0I}N9{ zeEE{1yb~_zo7V5|muq+wZu8CH6)0=@w)kA`!*k}C3FW$d;r!Sb>%4qwA-Q+zmtAdW zc~JzFta@crp);cX%O-Fo_U)?ElUU+vlP)JR&hIE?t;f)uNQ?q{MjzdvjN{3O_#fDz^jo-fAOOaz`X4b!sOD!*#(pi|Aoz9+_ zn4ky`y(^J$9M^9eF#tubb8j3b|E@JyXl518R>8H$5b!(X2U~mBLykC=hm4FMwan?H zJVV7FYV7_mgQl#)+>%7(zL<@<9GC4UL4T%RhZufMCo#fdgzGG%;>SZ~p{Nu^kxh34|3aa%H zeZMF0e>>t~amzlo?dYzfLjlPxE=EoiWL{i&aPU!38yXsFYp4);tf)IVIZ5?4?D1D~ zjZe|~wYRsMTUdZA5gZ{XCl{!1-9z491O&M6ma(UQM%Cn}7Z*P!acRiRya33>!Ni9t zdnuLEuC*NuzBt|-;^OjVEO{Ny^E5Rosy}3`A}2p~a;eF~*WSSaNFRi~u3k8M{@Qpa zJMgvPUJXzOffvU9;&0w}4#Urx-ye%$rU*H)u`{x>3bPub7QJt;3i9&oq=PuwAQEzN zpeqn^s#xbOTjCe|L>~Ro8HXHDo;eHB^nUo(_$!_Ez8RitGP)meNfZv#01*QdnE$tu=F5VP+_s#ivP3jC;qn3^= ztt-M?Uw`>S5N_M;O#vJz>#`@Cs6>0(L9Q1TE_*YV(eR}W1q>{$BYk^X{Gyc3?$`&AOEWmsj=g-~wdA_*B#QKz?w#^}p>2Ocv z50lg|{!MVJZNq%t*&d{h0<_RLPsUyRwr>?lw7zK~`ky02+tM!5qoa%N0im<^*=}BR z#lqoq{BoQ4&zP9eZO3QNX1xAfM@JEmXb#{^H%|;s>W{u4^o!Wf9RWj~9qg;6CEap3 zB`kc0I^zvSDwwiu7OFeI4^{4?m?sMl)Kzp9n%gIV@ZWIy;PLeP`HHo5kX*+0IzFqa z8u(_^r@ifX_3OQ_#s{lYymv1~71eSm`0rPWo3ls5>S#Y|YtK&%^o;ZrYjO9qvCv;auNfZn?uR8MiHc>+(U!@_BeF;0QqL44W0NKGnyK} z$Xtc<(q-clle!kn&tbIl=6}LmWN>fRjtib|)eKS$hn3{&@Gr+T$f*+oZ$sd5G~Sol z&Z?KM@-t@7((1Gec`i}_aMpT(s*0K#N)zW_pYlldIJJ9_&8{QO!3VM?S%w ztr4gVEF0?~Gtey9Zs9wm?6BGAA&+o?Ng2GgP<6iw z3<-ZGt)ngTO5XYWz*cXE&#?QG-$yy?{f#26aB*>3zaPQU>0x3ynOPH4Qx>^-%<82r z@ARnpj%}64Q)+#3A<9Yq*t9gXS~aQN%tG%yWCsRPQpihm8_tTBlWO8N<44>vsrg^! z$dYlL?2Tbi|4fJ>Ds|W_%gt47H~Ub#YGH2O+%d#@Wh$fLW|k)8)udXYpt2W)pFXxN zo}OV1y9|V6sk3TN?_B)5x8m$2x znX6%+`d-&jcRVDF5ccG8Tx$7!a3C(9`UYFr^W-)Cp-OjZQ5rVbhL=v7cnf&HY&fKqyQ)mP5!h`ti*IOUdWEHz0 z7CORUSJw!6jfA^uhf-mVWf&3@0Z&I;5*qS6_Sk zNPT@#@K}It7YViFY6-O3YQ}T_B;k)Qx+poHA4TKoIgE;(J)E2m%=5h3IwWMFzWfn> zFe_^7wX)%h|D(Yv*kL=(4d3GrQ%g}=-9#TV?%P3UyMj2UB+F{qth!UO{mpN4wV;U5 zexuT?R+6qssvh;`kHwz3x8}cK8?af>zUVy^6?wX!&>>Fq|LA%PsH(Q^Zx{uo1*Abl zq@)F;OIoB`TDnWRkx-CQx;v%2ySwASp}V``@Gb5W-y8pTk2{8rE8}qX+H21>=lsQ# z7xClf`N!78doiTk7jS@mK$TS9*j_kS9oNlqeFORwTR+EweaJEb5lPDx1#7zosEZbr+Js>MZT zUy3p_DIkRj8AHopZ&AewKoGdh;Bg|&C8yQ zH6fA`YNn=YwL$s26>8pu!gN*#No4(K5&)a{RJIypHpvw}v-ZQ(XFRP5k@lm<;qTo(3PV=_ssYO{355^8jX z%}fe@R(L5%K&GUrNiXO1vP0Z?|7{Jw?I{*cwI@|>H7@5I<<_Q8>Y|MRKD5if^(!(V-r;l2Mp|kn>*?fqH^*lzF{MZ=?=goMunbtP zZ`S(jnk=?i!ob}gDkv;uWp*U< z_QoUgKF7R!`85u}AOE^qViFQqZI(Xz$ysR^0riUGk#HBHSM%Tut(lP#Rk;la%wC3eAv>RPL4%(jOKSK>zJ=@C*Pr7$0nR+~DWY|w@%!;*1Sy+Cclgw@m z8W6GDn{=cJgZ@JpQC7QaL-7wW9dvZR--AMQ_)Z)agsN(4TpUbJso7LQ?>MM`GDHRh z*eEJu&#jPDcd|gQ3QZ18=H;0pUJ{~pKyA|mW?C2iHiA0)8rDd&d%7ts{(==%V^9avc55kr zy13cHoc`TAH1vmN@m~NmaY!~Vp07DR9;aU6+)&IG5fhDxj8sukc^$;p%FAzku{UxA zPe>}j&c>FNlXH7*Z5l=;`<^|tnY1|nbdhD(RcEmyGt+vJ2xu%`zUJE*&%tnX|CMJt zF`5A)1PBwO-JzwOqa$0`PM3^HGMj4T)&H!F)_kUFv38?pr@N=t?n(u+)oOo8Tp;pP z3V4^r^+Ky$+*fuLZ{`^98i_hxOvGpdbu!(c=M;kz>nz z+c+){S2-IS8EtJlJ3A1uOs>8kuXY^=KmVxu-045A$iSGYYdge2t0_^&9I%_>E)Jb$ zri;|dfs=M_ECC3R_~OPJWfKIAqaW1!NAdk5w$lVT+(CC?%rsY(t)k{kF;w`vc#-hxp zZ%X!Bxx2?sp#G>DRhALIe-kjf#~#+5&j8brY+M{%uKGHKTqSC^pH4J11-8-kwfhU@ z9D6NKK!kGVpnP1kV(^43B5^K!TuStBLa@PR+E|3yXUNe66%>q(EmT{&Cr?uoPe~KU z7RL#1uWZ>L9i?dr)^FQ$JO0)Xz@F|W>>jk-PG0gjEi3>hRw79vSbyonoYd7hl~tC{ z#!3zjgH_)9lv$MX^^(M)q}KH_(VRGNTCW114152(i3oSJdA;C$y2PZSwLG^1{IBwP z`D8mfj_7-9e@}B;hcCkHE|V{3D}9n@aO?6LE0w8%!6`n%zn+1X%+Sw{A(_Ya_7?2~ z;?4=<*&H$&N?BD^_%X=J5m5Dz72-E8~tSOp+ka6kS!doJE) z_(%Ts9hXQ))*KDXT^j(Q;bMD|lE5GT|K&Y|0D|fNavh2L8vZxye*FJe8Li}da3)UR z)+Sx%K|pIktPB(F}r&XS4q?YEZ>yQ|KNUG zz@+%!R|UftDl6g$>EIN8w=sxXNqI>k*5z=LO5)Ylv2Zy}#4K-0%IHp&J-50(Ha(_q zRR_TI5)k;d@$V5TuP0_FCJYP;Qo>J)(DncGkl>o1w6Z55%^W-o3)mu&vBZJ>C}1?e zX-thzL$iI+i+=Za%ih)fP75BM91H8u&`{RtsA)xL@mXak+lA2Wq$x~xxx6H`6}fMk z_fsD#_kyMZjoe=?O*-xrjaPpo{kj$^!)M_k3oDND*VornyzVV7cPTZu0ud5(KG#pY zr{b(F%O@bVV}5~2NkA|8cr9I09L;xt7SnX`eBHO}A1NtbptIt=uQ5l?&@#SQ4F*w} zk3!UzKu-f%08>To_XDe7-6#0DH|Px-fdX|_4|%cfBc2ZNc5&gU+E6xW=t<3=XM+e4 z)wi+n#Tkw(d5cCN1jq{l2(~w(R|dtXtuA5x_uKzetd6@kl^Ru&dv2F1?bu37BW5e3bz-hQhD6goQEBSv?9BS-5)~1BXHa(5Jj>Ls;(h(30{7l_ zl@W-Y4<_=^cc;LQ?4!(%LkKIiq4V`BV5|T8q=)ksn3dg^rWe80drz>~>?Mu_3S}Vm zg8N#!=Rll)^!6u9l0*F zvTkdO9Gi*=U;~hk-)Gx8;^y+ksW%3z4fvl}z0H#{tIf;!=y0 zR^4k&CBq^mDY?G)F?*VOiUD^L;NPVJNNLeK#n#=k>`_WzpT$ekE{0ZG=E3?>N5i}4 zyaL^lSq4`JV=3eMotW{_O&6OsEyFxHOijza3&TQ9#OzQ7L!di`uZ?+xG+(RIom_uE zTO?u@blVLd6PUG@V7K|iZgaKPP)Wv@;G*(le&fg4FsX(6Vz64c#nvaenXBTRW$|50JW?5$7@Bk7yht z$G;z4+8TYQ!IMCvNDVAAKpU8VXUOueK~j;eKxaVn^>^HZiM|WB?;8A2DzSn74t&LI z8PDStE-L|>4cR-MXZE~gWPc+gBP%M-z81EIzPOMm)@Eb(JluQKBH!tKvb>poln^Y% zO!XP+J@@^SkYGna`)%!$C3r`6c6OBWRnZ8!&jz+ntq%HqFZTL+u8sg_33%|Tt3fmy zYydCV9|rUE_kU*3J>TzU2>`?8o~@A)c#uF4b6mf7=3CnRFWlGP-`~(M9sIh`>1M)Z zxN`R`2C4hq8Wz}&@GD({Nc%Ijh1uB`B757h8Qnp@g5Hv2u*hEDUPwDtuQQ9w0#e_r z0Hjr^6JfC%F`ef2Kcj7JZ3%boglNyL^el|mH`WoJAE3Wj0PeoCpfd-seAy&k4u`+j z2dP@<=;%-d6ciNg<^@%4ZL;N7Y!VVJt3g2|zE3s7(d^X)cX#d@>L!Tfp;uNjXIyj< z!*t(kz$Uahjg|%^O)SK>95*+V&2IgUC>rq3EKvom0B!}+q`W*Dq_p5*jP-RxstD3@Q||$`y*X)9D~ghU z=eUOtV`V)>pimkcyYiwJ*U`*Wn$z2k=YR1J9!e7g<+n`BVoRp{Q(HOOKk21+j;gx6 zz%yzCvg`QX6CJ(50P~y?^vMh7(pvTW z#Z{?iW|$wK!H+hB`zdD}cfTXv3Bn{^JX z#v0t*_0akUef@)9zfg1p1cJEi5~J&0hw(8R-q`PP8ZYAqGUW`$N9p_y!*q=aXL0BX zK0CAb8v!w2UpCR+(IJV=_@WNI2?W~$el)Dr#wVA-0`M_5a(!o zp)V`1$M?dZQ_;{MCcrV^a!Ou@y3AEsceD4+l-Q0ZFD(IcTSdE54(r7?ehbZB&3c3J zp!Sk{wf9ix5}_t}vAH*?oeaKk9fMACT-4-H60qs^LSU)1yV!UPN93@C766E5 zF{fFlzDrR{OH0vi$;Y49io{TGVi@}E9dP`Wlmyg-!XIKOPvAe`Lypd+HTdU*5)*uU z(yOXosO&AbBa(}o^!L-*1Puqy+0ffv<`xv39ob;x#{f6r&H76iIft>rdAsV&?rgX9 z`i9g^HCAx)YgB(6gWR!s9LoXowKPVbX|t73VBX1^IHcq@XpXX3Ab9(%vssmw^ZK=_SG!MD4+I?`YV2$+n#DY&bj zh;#q}&&?N**5;h{SEQ%_2=#S$s)b~3T)@D&K%LEFwj!2X-iV5-^;Vd;$wL@vf1$oQ z`@;Nexu+)pzLSztUR)nji05gQOOYBmH8sA|?ozk50WTH#v?A>6YH z+tst_;!{y1kdHdDN@M=m!yy1aWU z1RydYnVFHn!DuQ{GBPqkLg^*BC0-9t5Te3q1UxNOm6d4$7b{HT%YYaX2`6Ts@&3Tm z8e2>u5X*ISZ^svMfCC2$0U97FBy!2+PDJCSGfjY70#$Wi-#4h$vSajb%&5D=q$nj? zTr!~M4^UizynyKKUlAqsP61_Z-R`&NPS?1qa$<(?l3shSx zq%<0%4Zwy`aV~JZdfysv=pUa#w!!`iyhbXhg}xUE&k^=Ie#pv&bNixPbc{85Q2&t_ zT-Tv+c+R}|QX#xuTaJ>_vfu zAe8pusT?ElW7`Z27Q;5X>}Hr6m_8>n3M{~9oXQ7ty1P|ZgiUD+^c%C3g-}>3iWmLd zXpbW>9-J>Um~?W&E)x1>=5MaMS3pqL1IQ-rV`mSb_5LY&Ws_>Q?TPBZehWg|Hi+n? zXJKY0gaj;B)igDI)JO#GKi=_V=}$~d?9Jd_Ew3UfVk@c|8>9H2aPi!(Z7X^<5%X|H zFs=x;wId=6R0$DfaLn4B6Zy&A-6MvF&+l1UVhQPs?S_nwyZ!$C1|rSE$9I3!>bqlR zhD4Tsead0A&{lW^dUi3~T=g#WI=kyMj8shrWZhc z*(h%*B?0~`FRQ%&hdd!6;jBAU6lpM#|Gpzn5r_vpZqL*-aZT&bW@Ye22#pilCO^d_@9YbI?cjqW}B*-OHEz zd-Pxfdk;|`xkh6uBWVyh+G%WT9I!YI@d5xC<=5x?TMQ)Bkgs?da_>MQ3xfIGJv=BS z&CGt~Oq)5dp&J@T)zuYOR#qw~C?KWv_4VD{xQad#^}~$%Y`R>TVjA|kPd1s?2nM8( zfLPGxwGRjc2)13Jgw+e(b2H99)t9cId;!_-XJ;Fo5ttxSRAl6P0yHkJXV4CWkS^KJ z(0E^BOpg4M6Bh0yZFTjrjGrtAY61Ol%NM7=$wV|5-}seaF)=c(JVXm?cJK}C6TN@` z9&G{Ne*46KtW1GiSpU1(8v84Npm*!*iv<%9k~n;^J|oawo}!=aljgX)xg*K&XXH75 z=qjo$`20of37JTKUWQ{##|spi-WR;JzGH>?(BXF`{KAK?Y580ktml?t;&(F(ZI;&A z;;HGAIF;z3INMfd*&7D%h*OF^>3d$~)QrP{cBktPR_~GvRFi&F6iteo4UDLCiyORb z%!{L^_ro+}%rkhs0kL+`!04A@mHQUkN$#1C^g&$r5(}Jicr@4 ze@ykA9Y#i~gY`x+VJ%^4>7*3TH?X8J=Fk*gh*tLP!n~j^78VaYysESFoyToeW~P$( z#z@xtm><DQ6}bz$z3r0Rk4h%?i;|K( znD*4wWxbBOj)mB+s1W|Y6B&Bp()H5e*$~alUO;hc8iC7RO%12bc3DF;SA&#nx{a8D$b_&501&OF5jY>*B?Z7VbgT|MM+5lJHwUOi|4W zGV&O~3^v>tR*b%(_jfleg;yibAlpA4a4G-%Y8$FlnyQ$X0!a;Jp}>mxa^6jp>Z-^EZPO3`h-S^ z`ZaIHl%@0wjhsN)lRhH##)cMpToh64{0-iYRq4i;m(Oj8{%CDxMaww6h%I?@Df1=y zUgh#2qp$2*h=o(~yVGHpuEux;k$~Mq6kQA|%8BIXU&8G|70*pU%lt?{`Q(&lBhb#w zkgkP+)F)8>%O~fNH--MF1sqnhBO@a%g`{6jt>-nTt3bRkik01+u09;{_U-~M)C`)j ziHQvzM)HU`qYg;&{vdGxS0V|?p+HTTB_kPA@l3JxqB#&gJvbL@<-aNR#-ycT&<*m| z0APlyj@QY+wyy9`VC(v-9zIFh))ral6KEojByw0n(*Mq~?u@iHTSIM1s@oYdz&oO% z-gS38xjW7DQU^sH>tFj74rwRn;wuc-8xsT^@&-OCp7s8$dh=8zBp>OPc z@yT)s+ZHhZRDl*3r}$V{woXpSGgYEm!oKBj=8rYtFf=<>+S0<}16xuXtL)T=8gy|@ z=)UoAva74B;=4#d;02P;_@3RAGmmx;l*^RQ>^1}HjWI62X(?Mx(gMx@vRbE!BK_&1 zKu!|eW+zsVnkTaRrKAF@*;elLR8fS)*qf&)v0ldK^fpc%RVsC;G3{c{eIcEvGPzb+ z6UM_8w7+O1s;N|UNYbjZzTzRzNgVvSwkZ|P{wl7?hELE&upb+9d*1h=@XzSj+srrZ zto+rm?R864#~YQhv{mZMq_XiY#ST%z58Nv`i(6=Axky%e-C^Anl$2FP@&uk6?*J)) zl+>E%w(|p#Wq-6~fQ`0Z`*2Py^-x|^AzZb)vrO>RR1i3F9S=~JrZ5Vwt6huCg`dD+ z!avZ&kOG4OpIzPRG&*k|r3rug_Ow_D=J^2;?LEtTPG`=SWCj){fG_iHyKnt)u~}J3 z`BiA>XSbB>++0qBo+SWWaJjeuq*ykI>t|8`*r+%}ud}Bl8Em>=00vWT?8Aj)jpwQn zu+}g&H5D;f`zS4KRsGUVIA?r!cLz(z!2#VG%nkbF+;hi-Coew=o&z2Z4q$O{5ESd2 zulMJb8-v^$blwpUAmeh2BUB`hIqBnt@F{r8Di673*u>0C?_j{@bVEm6KUA~Mxv<@A zxUWxhw{~Z&)4Xuzxrm&!PVE9@kj7ZJ1`6#BpMBt)*{=)*b1Snf=hgO6II=+RX;vZ! zv%gITf1rVtnV?u4vg8<0k|}~XA)uQsxKsojIl7M+QD$c^f5Z>Tq)DQpp@45SyS5v2 z|3yJzr)I)1DJLiGdu#W=0J*$8C@9k9{JVC;GlghVv!080W~o?azVXxu+jQVzYJ!~?PlEhEkG<>mM!Nu%rJ z?2FuLz$7avvGFn~nR>%uY&`uU_s_7Qo*3%BB(%Jb?%2ZZ9JbKkK{L)f;hvP)^+LZ0 z$EkIIN51{%-T-d>&rXsxchu-WFSimYXw{bo>ywM5npJB3;lvh*c3=xw8Th;E;4#9< z7Z3nDEIo+^axFOT7jKRpF#~MiXt}(cMlJI?224U>`Ji1l(Wt*OIB-{?w_Y|{=Ng1R z=O7glAR^>`XFM=>e8GY(eu-|q-i4G{vj3v4Eu3z1d^y&&xO^C$b4*j+zfSD0Rv%!E zf97BgNusk^SexzdP40q^Ji}&r>}r-OV$r zN4R-Vk2J`@ObzBc^q^L2_yZ~{4S#=s;EN+6BY}q>^L}L7slo|!y=<3*!-fM)9JPO2 z^UoN+Eii%-?c=T9$TmC-oCc4NvW^~nR8m;$BjB<1 zfoM93@R0~0g@pxUuufl_zq`25aAJD=!GB##$SVFHsrBCz44yA;F))Y(WuiTA^q+!` zT-{n`4XU2b2?Q=yx;^4S|GorZ0fu^~rbZYq4?sKJvS62EC&g<8^oA3gzp~ph1H7e~ z4tz#oY2bN_0ro23NL9FeE;Q2RB0ossd7AY02DuZjG{-Bq{p6o3pJnV}lr#$G15fSd% z!A#A}${#BJb7OFDvQO!;r-53D6p8L#kM$`9tjWWCNHgf7=vhpLucDe7B#FmMUtnr_ zI*HH8b@Z(U4NYFC#FA4Rw5;Tu1CdLzL6I_8v4bu;x^>C$+jutL0>HTT(>;byBY zNyBzuHRqbX80aCabYT&uzEj`59ITA1B=(_HJ1J;Qo{fj+=G8?y^@jBwO-|Nqdw{A| z4QMG|EV}>whDWmA;xoNHJ(`W*GVT?&d(>lbaBwz<;=Fj-yE;08KW72xLrhas?;z2I zB)a9$&tY$;X)5s7BV;`!9jJ^i6`0Vq*XGE8(IRWs9tg^}S8il)X@$rlK=W>nV$V92 z^HAUTpNXCJxI6F;JUoxVdFhh6g~e7`Ict3{u3JwAFhyO?u5|4umfO;WKpz$n(HPV% zRpCHVq|@NcNKc=hle2lS@%_Hvt4+kWGspt_^#UMSPmU3->{k|vV;BN#f(cvajbCu^ zx$or<@Nv3RZ49CQ>u8cekU$A0!0FBXJ8OPlhYqT_k4TgMw|Wfc5Eqd+LDXx$!Si%V zr%Mc*QIogYCo^v4YbKFR}PaoZC?>9^6&sV$mM)h zrW64uLXGsK=C7ey>#y?tpIZF*xb#0s$dJWFNsXj?7l!_5;19-{@|%vW0reTM`x`L6 z7lT&m&oF9%cDUcjX2HPvba7b-l^|v!-u4&r=t0U@Cj`6KWQ<`!7yE9yN4+~&F~QkJ zki46}+Eo-}cM{&^4!lf%Pfp5G`M%T89D$jT{}+MhfQFd~~x^zYeuIz)GF|Dl_F{Hb?T zSXS0HVgdz>V`XJ!fGa_pJn}(>)1@;_E%o8uyB=e_j8T&pp;g)ok4*7zr{7eI)oP*b zAu>6v1Zb7svXbpPSu{a+y^mGec7iYvOVnqOVd%S{W#dZTh;LSWz(n%9G0E ze3c^g(4^f)tHPY=cyJ7dhwA}24Yb7cx^v{x}#j07|?@&skv z@rkznVTqqi&MMVF^h-f}=@e#%v`HxF)bSBc5%KPumqbx!&A@G|S2Xv%B}=*cB2tH9;TM{@168tDp1u=@w-SumqA{CbFn{NUp*T?nXIN32Qsf3 z{rxOk zGQbhz>a|v>B)twzox^4Z|1uECNx(xI#Vp>{Sb@c&L09eq{u9Y(&kdpd9QgIdE ze!pmv^WrCJZD`&6FuAk#?~Fm_DAD{sTmXQ&|AtGDc@GY-AA6|vHp|doQ3p6ElVjg5 z2pUTVocjCvDJZi*RZ~K`zP1MD@}UHwK$`eMEMLf}sR10sM+4RN70Y_}Qx5;n03&VH z23zrm|GunSin@kI|JU~)5KT@XZ^67%8pQ$YMVN-TTja;WB_(C$yn?$${7D9SqtgNO zY6hu}kT#MafHGlH%kugVY`{fDM+2TC0pTv|A`5%+C#pZ332Mrn4q48>;;z33l;FP3 zf6Xsd0&_yfh`FQJ_xl|QLjrDw_mK%w>y_Uo?L{y6NOvz6{xH5agAvq_zD|aW6ak*Gk9ufYWbS1UcUqxP6 z*v-nSPvLT-U2gS&5sL!LejF5mO2xd$XrrS1_dt9l;Bpdstq1=18P4_A6#{Vl z-|f@?E?^Ej@|cQWqyb_wRB)TBtA6WY6`~K|V|(t#iV8mm0Ub{nk*A*DY;RBdV`%;4 zY;ENa7tDB3$^F1i5AE&>CSYda-YHqwVs2~O>tE-kerI)MidwDa!}*OaTjYDizW*%M zsPhnU$GDRD$-jnvfdzyq6+-M+H1t<5NahX}!>mnmyQ@qvaj^}Xc;xI-vo~6_zR!UH zIpBhKIvjbG%4PB!@8aP9XeQ}8?En{v$*HNe%}uZ~PY8cvC&?7&=L;i&K7O-n6b`9Z zm{?AERIMAwi(5TTS%k{`eDjXZc}5x<&xE@TsMPXNi7rbjLE-z8txi?-@DzO?-plg|44X0r{=D$#Vfq5UFY+BSp*B;vZu#z#6-Gt{m%aB!@6P4 zj;eMD&>&?^deMd4?~a#gA^59lbeDI#UMPV~uEEJ#&dO@&C&qS;Y)VJyDX;v9ETWYc zEzLOryM^;n6CZ>NLP*N0ryqumBZjn(zED_{jn&}PTvBqB*7GohXG{GKhV^(PJiEU- zf8P{kWS-AZp6Q?kA)gkQ$(4St;YHTrCVxv1^4NARlpiEl~y97qZ6_xdgvqk&r&DP zZ{}K$xuo$lR`8omxZZxcy*d1wjeT?TglGNHwPV|7g1*mJ*&}Kg6bBwh=*7p&EayC2 za~KWG0GOVKXO4q|!+NgHSLE$7)qo>jiYorFTun?-svze%`Xvq2ix$$tY<{l2*8|Ge zJ&aR_-99=9U>DqmTkS`fZD^l26DiDgjTkLQBR|Fc#}<+YQ~3aOyR7oKulQc_RX@Q(?axlo^_*PD1D z4-y2vJl02C(#JZF>_E7dVL&%cgG5S@=oJ6UW1~XH<>|)wz-tPmqxE@#R%-}#l7l5Y z95-rU>sR|Ph9Ch%I6xudbCvoziG(m#1P>HswNrMJSwYh?O=t^eQI{T(1%8y2vmc6O zs7y}h=8ESU0l9IYk=$zGZr5bsk0gFEGR_mKT*d&q1#jKQqVeNgS-A!U{aYaJz+$=X zmR172_0Gk5x4jt&esW&dyZ3Ke9)|Ya_W!(2k_iiIHvjsS;_chEi)c`FI9;v-LW%p2 zmleEEKv@f7Uq}emo%_3N@Op!Wy>H{eLgVcSS-YmzJXZ?;m?74ggr}$H+OjVVO)tPS zeJg)(@-VHRN`6l&(kiy}#MFGyQgd-p15(w8ao`OsDY?(fI}{P^ zMC&51sdo$b_3IlRe5!BdhLs*1!Wa~q7!iT(`Nlr}+J5FePDO+_+(QJlKCqx;;WtX; z_Rza?ZXX&#y2FX(fHSePbjBtGG$JDw7XCyYud4F$7F9r`_Iy*2`i7M3>~dejZbSXZ zt0;AyXMP|K?~y_1xjzN_4Nt*hIyf73?rt&Em7QN%S6XU2IExFCq_kU%j#nX*M7+*` zY6kFxAutiG#r56^&I&&)6+h}wj1;gQXI67~c~yXb+8K67sPTPc!^rxOt&h(&k=K^p z*}A9RsTV+h4@x_56oaAoik25JeqMTG|mxEy;M36udK_XHO>5}JtbK*ACpvFPZiAh0{0 z69$NW{ZQ51yuAF}Gy8A$^77j*w-PBqz7;J_?)CnD%z?J@q;|dIvwJCo*wi{4tJAI&UMT|>LaPEJ|M_df%8_`d zQLlTCTNR~=Ww6DobX1bo!RQ;%L-Ov8kX~r=AjZKVc=@vHV(;hQzkMY~_e|Qnz#3w7 zeB93I^mlZ$*n?*tFXeiYkI%|TT6^qzc^QkaurP2uprd`qsHSJFr}qof_5Uv2jjqMR zUS2#$)T(s{!N=n+c6o2bW@k+ed|7~9gX#rR>JjrpjI+^ijCRI}2uk@V%0nYySWZ`&OPHfvRFbH_F^usu96ZGcIGUC0ybdUHpm$U83qwDS_praPZp3H8ko4BDA8YB z_7buVQcK4J2l!Ln_8->;i#-o5AhFtq)bsH1Nec?fi)(BiG}dzzE$R)r2Nn+Axu9mt zrUW(;uo@No{yhSWBctg{+u7NH*Mpc%WeHXGZ25GCIPy;n($2m)2tz!-2bivK>XN)w z?nyq(Tvn2(Ys$>SyiGZG-m*WtL5OL$(s8V^IRIG(T*;a9;fT3p_tSTbuo(qT*KpQOu+a(lh<8Y3HtC$pOo-phUtYSB%-2E}# z32NC8e2MWmNTFe9D_8HZjD0Y}qoVGCO<)X@@=O?7M{fRYA0z~<8uBIRY}nXTC@b%? zVOT%t!EwL7^ToDjc^OAfK_-^15U3(4LBSG5OTqa0WNf_3@k|6I?SkivLq5 z?DBYCh(XV9T|vR`i5RPefB&8>?f{#riMDTDLt0PR*w~Q3ZT;^Y-^#n-xDB#f-@<~; zLny8{CV(q}0dp-ln1k>&?oVZ3sbv4J)Zg@sFsWD*Z<0;=yZ?#93-ofaCN^g#k%dWv zk$Dl*1zO4q3Ip-Nju*z{`uD)g>xnW5K;PpUbpzzHUB5uj4#DCQ zkfV(N#d&zRx(cruaKdP;4j8kIuz^0j(QzN8JbG9yuAdJ&kS+DizU=mFTb~#mbQ|wp zfMfs=>M%sbz+ad#vj1O|eqQRE2alWEN^@S2U9+(vGG1NgGXSJl;`3*29tP09A?8r= zf^yyi%`ui9n8}+6O=Qw(l1gDGfAcj5jFBiu0g@L>%cK~lWOBckula3&66RA4pg2ZF zX6EFGXPsQ#6eksCL{EJM5k?Wa917J;9$cdzrrab26#|$6P+n~p852Xw_IbJ!Qdw3O z&bHKtn_q2l0kl|(3Ol+cCXv_r%2?ICBKO|4&c_XZN}Irc{_AHI6Az_Mo3_kW)^IJd zit<9)A>ou728}%m6=oQQ#d5O-)KTGDWg;fR>I@Jkp)n);%k*>;+41BZJo&*pnDb#0 zPjlR*$7a$mh2hIpJArYYO}d510BOJm zdnXDotO$s+_!yY;#8aGeNlB7_zxR*04<-?dfeFB_!d@K%D;!%Eyw+6F7BL0Mu&L^DG=6G)f-F| zK<26srG`H(mg<{>xqnenKdl!_l|@7--W`d6!}4P)sUyDCa-QNuaIn;i*W62XEtgBJ z_bSnnVxh16qr7ep2JwdXL4qM3dUG&SyMI+0p{cvJzIm|dm0MS5){hsqE_0EbJO|a} zI}lmqUMjbq{Saa`U*~c-KWI=Ulk&?9XuhE3z#NK5m;;AU5+0PmWc_kEL=y2@;ZxGZ;4 z=>5A#b0RYNo_BkckRV_m{qAi2Z(3$%P03Mtb~N|xujuF(NsJC_B@bXC*;J7Ru%pql z;y3PF%S&{UR!s2}*jP|vbMPDSxkd#(x*AGKXFCoj4YjA&T~gREYJD-`9i<0+XsR;Bbcgp!u!V0XpjZGBsedH zfWHEynS`3iA51MhE-Xnedqw6~I2mr>d(&cQ@T{KdR@}a%9HqeYT>5db6D(9v)&v&u9Hu|SC(k7-~6!Yej!=~^#@&8EW0#06BB+l zEf_Zm+~U$%#>U!cdEX8U4Sm-`gS+Dea2*_6Ga_Elq|$>gWWvw+Lgs{wLIgV-*=heN zahI1l7m>5;QY9THj-cVm(Z~)p|V}HG+?_K8%Pjj}+{h{^s z3|^Qo|IgCqCO4}EZsUW3fdN^~oC(`j$Fetmj5!os1?I?v0DlaKJIRZm>W*~C~YUSw~9LVOohI=Y_j1?_yHju3;&>=Yn;wr2bfh{R z+FPD{CL)U8V=*%u4}Eq1I1xBHI~ueG&bH%6N2^&`1*A9B&h7|p^o<0-#z;PWVC(H= zB2FO6k&)NevAwhz9~*PDvx|w`b2zwzboe;~vz8dp*bq-fJUdjKu@(LF0mU6V2s1n) zN*DcFKMlx{$dq(+4%aKqr!4dt_D$2$sMVyX z4jUVInvEWAv)=hwfzf2%Lq;v$Ku}QyfdJ!}_Nbq9bQp`(Ub5gE1>GnZ{6b%Qq?_t|2)#I>qNjW;T&K3hv`i?09yh;*oE`qjk;>LCm3M~n8t7Y%*=OzTLv!vWl3N!@ZEQe zvwj_b8S+%#Kd})A(S~E0^*u012YLs4uVQU?lqk+AmZpj`KbUNd=r%PWN`C$f04Wf_ zTQxK+EYN)Adpy4y7$|6J+OOqWTUl92Nc8T{aR|6O3pn&CoeY!%taWFb5CP$4dzbjD z7jC9A`85F+S^U2?Os_H0=!ZAks1lVT$C!-cpXB@CNqkPLhm`4FQ9Tt zN+1TIAR`xnBCoHn!G60T8t+np1koB>x1$4SnS8(l_AP@XJE9!s{a~lz58yKsd#>2PqsZO5+`Ah zos`>tMlhxQd~w%!kR7&LEKoY1k%V4#x_iE>dVFjZAuE^T(Fo4pJ=;%a_MfN7;Q zhBs$_m}nS{9%2*kP9Uc_KI{m9fD-NRKlz(XxU?J4oQf}DV4b-NORCGOw-*|_K@nLR zm953o2r9R4)NY53+N%!)VNt-#h|hiIu$aS3U7bO*vM;dLAe4Yv!O*YID2!nlE3JG` z%4vT=SKpFH!huCK1DvM7Zrp9=y`kSojjh+GPxh*+30=eqI~@|j&oN4!N+cv)#Iu-` z{yE#CWn#GZ+<+2)O%(66;bO0>vp2LRauh{J(&%_dkeeyOhg@@xij3^Ce*uH7{;Uj%+Qh>kBuspW z5*Pac`oRO9skt*u@0Xm_Ot--4Z!J6fKT zIs9Z1z@p%0*lX+m=1W(XR;|zB!+Abap^i9vC%^dd=Vu9!%wPG{q>x~=fUNxtvRH0K5^uk7rt84ME&J32=H$W|8><+{kY zT$rzKbo?3yCU{w+OIMzqVSw`k{KD^oFrb(K)kH~Uc~xb3SD%gJBf#DBMc_o~3P@VP zlx3c|0SW+4oj)*@f^RBiOjJ2$^4PT6e;n)#2?MV@8^{1u3SPmyP~f({a?e}o9cchV zwHHWd4|lbx=yQ#3Os)sKil2ynk!oWo03Cw3es&q@PD+yiY2Xd5&0B_N6H ztgKLhxj_d9E@0B`+FHyP7vQOafx(erC<&f|Pz>VZNyX^DY zZ74}gJ9Kpim&@nmNRONB0-0~>y-#ro{61EaOta^{%B^X&!>Xx?Nr1;y%i!d^`Eb$6 z$v3Dh?URoy8b*R)5*Zyo@9d=hcJ#D1GN4Olq#JGwwKOhRkF%`wwOFtA=sr}vNFxah zxN43#0n`^^;kPpaW7`TEyw#@*lw@SKpq;z0TWuv~2g~J^isP8$bNdWt&SAQ_Zw~Dn zKO_^kYkbjJl+#(nbmKJ{S(-sa-pl#G_CZUFefi>(gsGffo0svZbB(pG0O;xR+~$Ea zpR<|MWIz7AMoUrU^?}oe-}%Tx4}Z3I23nfR07DLLYvvV6($lARGAV(PAP!B8s5iMA zYKVi_wIB1fzXL?o!NCoRX4mWOadFX3&w~VZeVrK~$=WBiSpzepkvRGPP4;Bt!h zd-GR67?}WyP6mdg`pfjg`R4i=Gi22#wD8~O?!gB?cxQl4&EZ%UnD7&!TJ*)&L$RbZ z@cYl7n`fp(NNHf)R1!C??Qt9K0FF$egAUjil?_T`pBno`tjV9^QXOtXa>v0?!bV_0R<&>2+E%-< zVaI@{cCw&xdS-c;l|67n%5~sb>E-d8Wir?0L@YpT_bpS98wm%Ts;cpj1i!8i?dV;r zoV$1%iN``!z=A`0Ig9FIgA9u>oC%$_HqTFV#bh>fM|oT3J<&(h!4v=2FVOrsy_Se- zFV=bjM-uW;Txo!vRjbCt3V65zrjS_7`R@Nm+E<2ExpwPfV<4p>ARw)DcStt~NJ)cq zH%JOd2#C^+fOJl}K~hw@CLxU=-5qD(TI>7vKHuKg`ElMK2s*FnWX|V(o-yum$53Hg zL5;=e;>?nUmX?-=#w#N$yQxO4p@z(rRsN~O=;VF6`PICUw(EY?5}{a7aWpjv*&7)T z4Gsdgq}4t{exqX+TtOgIVyJ&uR_+{z;o14jrxSG`NzYNQurghihCRcJ`Of$+@g_Ul z;>oF6I!a1P-cv}TC^I}MEUvI{ZD76uUf(jq5&y7)bNun4=tLK`F;~Cw z$JScds^E6DG5ZzIvQ$M<*pQm4A*4vl%^mc_6oCO6l2onWOkQLLH= z8#laG5SNZrSQ9cPRM9TF%2;&O7n!ersP>*wF&#=4Dr|Vj8@kA~P?w-vmU#;Yo)LL4ItDBXaHg2ZY0{`D&d7Y7)qKR15=7KaQtC{N#%e}7b@?GF*> zA1e$E=BMW*(votK$cJG^PswD8xSkGqjd4)|#TgPydEFkGS=6oQYOD-oFNKAL%~j0+ zpu6|0GdZ5O{IM_S&!BXcmT+mWnT~LKZ0*lMOUp`4z5S_M7eEP&t?^oiu5q>4*yOMK z`o;Lw&YL{zF=6Q8Uj1IiPtHTr=P#V3ANwY-SJG&(03T|7FgMz*zs5S{+K^0ih^182 zOOOd(yLPSKB;sRK3eo_4lEy1A-`v_>{S5-K_R!=F20F)`MMm)0^9;tPCnXKyHqEn| z#1zzC<0rtHA|@HptjzR9dhxNyW2P>XVS9zloDh480Sxt|W3l98?ebP4F-mD<)Vj#K z&}ROZFlYI5ja~s6u}Rapn}?dJ@Mi=9(^UgnqjgV~Rd-X6-2}BN5N7;vP0Pwc4WZbP z%OxQ}1uOiFu&|k?rtY|xODlRWUvk-34*+5V2~4Urmgz-B0ab?yPs1~9l|}jrm(H}M{lK=|e4 z&Ydyf(x8BVubF*uO#Lh)H>tkZ$iwnmC>pN?trTH)_znt&>DkS86 zt!L(jN(@YWC?Nzzi4U0=Ylz1g(eCV+<(o7PK3X_wC#M`%7Z=Y;O_lz%qeoo=8x4|) z(FM;$Q1D)j^bVW1I_>2Cqtv9kk2pXI!@=;?6HZGBiNGkH%#THk_;{Ft{O_o%pL}H- z^f*rX{tPv6FN&gCmOL2LY;S&{uW@6g?pca%6-c@J{dsds-?jO@Hq^fo)M~!`4F1N8 zjg(9{E%5o6L0fcIK0GkCIhB<$t$Gqt-)}`|UH5r|5f=69O!?WkWNMm~;)<=B+S9eMfM;(H_aGj@ zz1nYGH~%pt$hMEhwG_eHQX4e(jH^CYkClZ@;rHRJ`f)>I~KQ z+v2wS@E;My0V^K<5`YI44i`>8&+zpTF*0bAn+>Dlk0BMLy!^pM%eH^Cn_F4wFC(4P z5IER7q|5LS0Mf$3b7f_%2lh0N$>&!qj*fUIYCvL8?~&hJ|4l;rq~Sh`K>YH5_Vw2H zdjvw{Ok+tCNZ^F0RsZuZk^(c}&t57=l-BP_?#+L_5V{${2EjA{SXrq z6BQ*ZF22FC`}=3L1L-%cTtwg?lBLB;F(W+MTJ9#|%R2)VJy&&HY)#4>f4i{oA4E&y zqRAz+TTYr6>)lJ+Z{#2?U+Cid(I91{jP##QlJMR=>)Ez4Ktn?axYxe;97~(t76Jot zfAnJl?g!Rqg5Zjl<>&V+0FxB_Ys*LUrJRKq;jM)|9pIPn7nIu;??p*9wkFE@m%X~r z0CKjqtsBh9ft|&cPbyQ2V2>B?;T+L@2T_IM=#aAff&#+HUoS+nrmjn19j-?{8;?7r zkolzXaiCk}`*KTuO7T{Mezp>&EFP0{K%Gy>@LHYE4fJ2N!!5|2t@$8PzqYxmcfog( z`8}ue)XoX`AgVI)CN_nnxVZIDMs|t=Ct(%Dsf-r+CeOBR&9fMM8_=vN2YqC2R`7V;ci4^s z+^F>C=H_^K#=m|0{^Et`DiRU@8ZX-kj&|QCY+Dx_lHM>HS}@!~%ggv-udyt^Vrx(wT!d0@aojdmT(;Fw2T!>jppTlSY6jRSWB?ls& z9?JhD;jKpFtY!4{e08=K&Vrx|>u=v5tBjcYLu~l-G$@=Gf)Fo9V&?7sQHNZLY+0@1 zmg@^9#KbL|)1h<_alC0aODkf3+93JY<1fBCH>O}i$`8KriE_8Oxv4HW3nnJ!WLG{5F6H1In6JQPUUVT(@wn2YS;G*&BroNG0EBW}sdSg*GJmw&xvmAl zKcWmTS>K_di%MroEN8!Hztf6@my`Hyh_ zE|1`TNWSD4^{=1csh(wC|L@*mJ=qQ)3}&F?xpT*1jO(3t%MUlhA3n#l*>5DP33&oy z8yeldG;wxjO(I(!6EoJUrJO3PlF4pe0$=__?(UV-V-}+YhpaOFHq+Vvq9#GHXR~Hw zY?N10a%ePKhaL=Y*_U;;6ydlyINa{lhQkHc->AqntL=5q_xCOS`h~sT3rr$oPB|#J zj&N9Z6xB`a8fcO&yxlO?!JB01$8T^UUi(>pYBt-HX=OgX>4y@XlKnJs;`{4O1)(da zpyVw1Ip z)Iz4zNVnMphspXuBJ{gj=K2;)uH~n;nf{w@d>rvndmig=u53S`jbm*e|I+bW`o#

U;a3E|1wD$CBeugsZo{n#j6bV!*y)&pmn)D#qSWMn(cRGIz1fjM-9%j)!k*L6qu zykPFE9$r=By4jobLE9VLnih%WcQ0-w6oPMne6*c2CW)HS;~#iz>YZ**VJi(P9q&7X zD2D0XyZ1M4?1x1pFnI9DAASg6P}Ij3 zDrT}QtzC@dVtiWv`LnQyK}7s=2A?}v=mSCotu_c$Ceoz<&!;TZK!_?RU=g#}MIj2- zg5eAd#bwSYPTl%1T`x98)>e5C1{7(#oR$1QtN~*8Vz0h)+I(Jrv-@pJ@=!v`w|-Y~ zm~P#_chB5{fQ7YG+?$1sabsg6fluZNvHfvZzG5Z`1jjUuJvTL#)cN2E3I^c3wvyzI zlyurCiVbk#K<8Ot{@AzpPp5hIFBk!xfGarbCR?ig=EQw0JhvlzvZ2(FkX0u9nx!Sv zb1zb`2fJ{zW$0~f%{P)O_WdCIK7H7&D=%Q9ADVS>rbbup-pV4(`wYyfhL!2yXc zY|aY5X)LjYNyR6G+nujnLg6Cgz9GhYiW~Hc-uO?AXYU5i%iKM+6mqLo-T<*A9uMTo$a>ogjb*45Qq9S$T%ZEdpe z-^(v_v$L|{;w^C_M|G)#fqo}IDcjOc`PkRq!9kgW>(=et;RD?hHXRORm%}^m+`L`m zx}Fq}wWxPHsByM^WZ4g%=I);3u+{j%-fnz+?)M0ZaKv(SfmYS))zSTP^yFLSaI z78hq;nht8!lUF0P%y$;w@^R(3-do$p&NhLtP&Za}Hc{&Ua+oGN2Q}4RdY?8Oc($P5 zz4f_2{i1NWW9{I$hkl1lje>8*?!&1nBvBjwRtRbhitnWpRaK4Yc*SS7NIc8$&gW(u66BDQNVk01okQroUQ1xo=tGw!A#rlN#<~ZR! z=Trnj7jiXGi7CQ=FO?$-3g`}mvy!!S)YM?QPe}9NHud>TE3>Ps81;8@KEC?3wInD> z0&mJuNI!42z#%TRh&R&L??-$JPl~TFG%}cJ`%!>h5e6`*`DA@-Q`6E`I{2p2k5<{K z>rNYOJq|3whdT#4_9PY)+kufFI(+s-RKC4~4$`Ik?V%r$Xe)YxdMB_KH#f`n|K4eQ z?3vm-op*y>v(gE$Fw3!W2s-I#@umE3zJeh*;p(KKGMvD7|32Y^jwgBv95%tf0Tjysgu!>F29maQzNgm6O$^&3seq=4?CBE_x%~+DL`dI1({U zOj)Q|U8+~5xYDe;wft(A-xY6e!E@^X5t_{Rv*x^8MOVAQg_)h6{Q!x;77zf5A-m;I zsFvNaE)Ty!-zVa6PEPXRVc2kf)|f^qR~hHAv$&b`Q)`--c-ZR1PF%1rO{_3Xn~2l- z2@{Wosw&6U&eN?6kmfWZ64z}-bxOXWr9Je(@j;rEd9Z63%DO|UW8e(g(lD9QV01?Z zau&XXzfg)-L9c=qQYW{-txTDRY#VLg`cA6LTa0Rl+ujh#h`mSr4+uwBkPuf?R0N-8aaCj`LO(l#l7xol1Q5Zf0hDJqNI^JgZY~{4$ops| z8W?`LGD<*F^?ZmEgz>V>RtE0&I9BRvhw!3utv0#_>y%dL^&nSqrMP#s*?%7-41WLx z*ldGR^%~Ajk6?0swDxlhCg-XyV+MDtxtNdkiJqnZ>`uNt;pyDaJ7O@^_y*booU@w! z@Y^-A-cxEUc(m;F`!Ht9pkS`6-2)5U@Ea1oKcBm~B}7E*FyV6pcQg=NiGUyhc%oSq zvNkm4d8@FiQ9XIGxQq+k`N!b9eor*#oDliyHM;G4iNGtZbKURl>G?sX8iA95f;)Aw z{G6HFD5$B~Jx;FX&Hf9J_&!)&m#7b%7qL)0W?k=`oTbk&jfcYaN9Qj#78YoA?kW0m zvmf|TK4%MvGu}gW98mcy+7RVMLXER-djGqdv+bZd*i~FvsI}ysR4;%1nu==gKK8OH zpPA#*@rxC`FAJ9k19Px`l2zOPX0rYKt;=GpqcGgMs4N`D@*h9is2bwCuM;{l?oA7c zal5Li;c{}S-54_fUwLD#Q>WCl=&_Mq@HBh*a@1plslaF$Y^jPoJ>gt-ciq#0h~I{K z%g!a3XzVXe{OoG`1(wHis}pselV66`k~yMBwpQ~#1&orv3(wMqzNB8-<1Mep;nE=4 zU|?sizc8>yG~5KWuSv#Rt5XvSEL`cW6-1?VvVZaX`62~fz43Gt#1|k%_N)jv&Wo&c z&keoZ4dSY+J$0VLNEvEoOOG>5{_~|Jy!w$IkW|ER_K$Y4@$&xs*%|WLqyC6$CnQMD z*xdZH$88jXcoctGz{PMLcnl33t&=hU41-Dxmc>CLQG7S{U0qi1V}8jlveFjueJ zVxk6SKfD({e6gzHqvHij`zX1b!R6H$>rkY9FP3~^UFkUfandO z2)eBrB=ozdr=b}TAeEi1i%^A8`O`KD*-v*q$Pw0GES`@42CgW-8fynBhjFQ=$5>$J z9w4Z~v?7&uSA+-&cg-Ry^KV5LBTJ`wtiKtx0|!+dBS)>XcY2)e?E(Dj!oX{ zfOX~Ury8qH<1;`-c4sMZlvzKaEr{ zpjFQ%Df!s|ozIu4;PBc^bK%>bwZnmbEkJ#-(2?+({q@n8zR$HR*N~o}7WYaCvD!)u z0Xnppn3GMnHftqabbB19f|}ytey*mqIFfebfhse{=ty!PS51eVors8mwu=jdLpDs* zPrIr}+uOq`)bcn(OkrnsC8$-oxop1_L#5R;FEg6tn@khqT4H~4`SBAFs~gO}hMMii zBz=JUwPQD75H!wZZ`jP9p`zVP;xUl~EU?nDIJeOVA@NG~EJghspC!?N-Bv>!B4LmjNG=85|qNH6v#U?<` zV@q&$|LU{j;hr8093oG+!gD;>I-AoYiJTm$0JkS9=yzHIVaC6y1^0jB(&pYM0X9B9A+F2Xj)~*EgdCxj;~3e~ zk?$}QZ|MlrK^r?>5?Cg-S!fGcblvX~P8=MF z>8gN*)cMT4Ep z=jxaq9*#)SEips+H79uhv}c-FDs(@UpW z{fK{alB)}->-?T{Pw42jw)s<;1Vsb{bSt;40lh!oTL%>)T%(?i>Mk)`0hm;^7J)%Q z!vimH?%f+09-b{9t0+u;_wB2YzZ(p9`uq8XMG{R!Hzt>s_np_@hlU;>mEAoih>L-m z_rrf8Xnp@9z?W5_1>_Gx%$F)EVi9NpVt;LN4F8ETo+oW}u@ldIoHg`XbgU2eKU$d~ z2eMsyIyM#-kBs~43d^5a<4u8NA_<3l#t=jx7?Bc>QS8aO zwd+-j%}1IK4qBs>FC*Ujwg8PcY#8$PxSs3V5r2Dl=!X*@$o(f z@+-siyrfq^+vNzr9p~&^^;`DjPOmXKi3DN5d)yAG4h^hknLRH|0$d-$@-F3fsk|^y(El(cbZL zXEJaefe4|;6iX@^92{7+4_p=;9CpB@oSeGj*$XKt8(O}BnVXAq4gv{tLS_XYSXmaYCw&$tomDW6h1SE`!!L>ZSVOAptJ4<0DHU7?|iz zrWIbB6Mi@PK`=@4;S}r zNy&B_>-IBO3Nl|fcfnOWtD(e!E!_a}r+rS{p9V^VN`C+LdN=iW7-SO`G*GJ3`I<*^JHouiEqt6lo zm%F*1ghpEb-hB|48=ILRRP&%GbT;3$A6v}HLPqb;uGTp2SN|uT^z&&^wF5&mXo0onc(28G8pkA8u^F|!*DoD6iS4%zZ0b+R3;pNRgcSQ1e+lG4@r**rp7K4wC&mNpC-mqr zC-}LIwYBN;Mch5{jAt2)|KT2@)2^#_C=oL{y9EX=VlrrGB_kzB71++s=D*_{&&%2^ z#Khy_pK7W&?T$Kxm;gKKCmWbJ)>K1g6dyf%_XR7DR=rxvexZ)T=H#lQg+wS35r;Fh z<)4CsgW>QWH_)aC=XClaNGfziUkD@@VIPVZe0vaHD1WjsdPYYbV=K`RIO|AigHEkb zGe00B??QlFbF-%wc0)0@#akx~PTF#WR)gc}fsN;Lji=Z1-*&vJKilq3yL4tmLNdnn z0A&>OQhn3ilgrd9d6vrnbsE6PD6*Q%wSR)fy*YICJO@$@trT8JWr%*!f05q6u+ zws3T#5Vs%u!Ih)aB3G%kN$cM2zXL z-&Hx4VC@&KW?^+Zmx#O=r8M9WgZy9!$l`xr7M~C)Nh8QcJ;pJv3!varC+f{!W0KTA zT7`ce1iV_jgp2p*-&Z0Y&A$VE;IIFTT~^~RIK+Bx>SCLUKYIFKAFW8Ji@j<7Yv8h) zpL2I*|65;Ly9Dr(ECyfKxDQTYv8bREw*1i60l%F`VN4Cf=Kq@Ic#wM2VnfAgK2q&c z9vk>3rJ!WH8l7ljyAGBy91CvecP9yt(Zx|0eYqw|*PVHfc8BuSfhKp)RpZZ8rZByA zX0~0n+~UPQFZm{2tD|7sQIIUdi*eO3Irt-sw=ju29nU|s1D_mZoh>DH|caJ)oS-kL$mI7Zt zVdMxWyj~c0WY*S7D@vX2F2C~a`JvVZilUdQbjX@+2(%Pbl;VXt5hg`^0P4E!#@&Yx z!EuibB(^O};9%u2R@(bj)hfG8$#_x1BJnqJ7ba%r<`0&Co2iefl2eddw}!{X73-y^ z)xYT?336e#)M%ztR#9iJb-uzS z)8bL*Z7=*gk-i%+ozbYUEPIo(7DoO6KKu=~_A#z?S`DFQ_GWWo8@%|3Jg*acdF$O5 zhP>a}VG4q%3`j6IbO{j1Q=;uUZ$w%zpvx->mqbttrs~yNoCf!5n_2)ODKpXG zwcMK~%xtIlAm}Z|(E+27q7=cH;aE?Pj!xF`kpq+KsG=N4FUL$5*SIO8a0w2(NF@&E zi@>*fz9Vgjpw1Rmx3g?!QBu+j*9YqOKJ4xa&8EZHrenMci?_qB;rzHmEP zfJAj@Kmk1zm0SWd^<;|&-{JMyWjk|O5-^)ja9wk|*XoQiHHheLU2D|#{d?hJph#f= zVL5{t4OZv&K7ZUEUx+*J(OM#>ol%J=OQK2XS3Rwj<{Kc70JaTYoGIujXdalA$Np{- zvFqJNw@QIK-Q3u*<#X<#VUV_nN$Nog>DjJ?bu z!}Cm|(K5BPw57*bRMlBrO-;>qONvy#4wi)~P1IOM2wg!JG#Ra}BCv{X++At?hRrIY zpsuJGo}XBj8*e8d;GyL)aecudcQ{XnDVD6vX8scUL#ms)%uGX}askIc0|3(XRCqfz z)i>aR=VcZqCY`;h;Ors_w48o~w{^#k>d2Y;RBQlelhs*G9!~dcIhEx6FAs} zz|*;!buWnHOmQsRn@tF^0JFkg>&VkIlPho3MEwczJWPix!aVfG$f zc~|1u`VyO&;MmXK%`exPA_wV8G?$isX{a7>a&w!ljw)p^hZJR6GfQAykL2`=rzSBQ zE`W~QoA(dz5hP*26JICKe)y4kae~k6L-QOlgW$eL(Zgut1_Li+m{>yrUX zb}RS&H4DREl=WqZk5`4rsmhVa7$BN)AnBvS!j$843NkXz80a2UkVUF<`1#Q^cLs!q z|36T0Ra~j>tw1?}1ZuDC>i|Cffc7TJUxv?AxIfIwsKejOmx#pGPMd{= z38FwcIxb}R0Oo+`vqe1y&}Nj*dO8}CJ|r>##B?-bs86Z(W~n5A!o#`Z`Md8eFkEll z4g#T9ERze>@d)s#U0huDLJbY=if;U;_VVHb+hd?B7Yj{73jFvt?{6B$Dqqd?NpJc| zzAy)h9OSaFmJN)kl$P3CODR;n{Ym2D7mrCv2dSlatI6AT7rwg6 z6sdxCL8gOgR;3(~OD$I%(RItTdv=3@1KLS`*V}+zWHy{9Cn;GvS0Q~t(f@PV+k$nc zzu$F!_dOP#m3w;^qUSF@H*oIblrdc9RO9r0E|2MB7Hagri4l<{?nhrQOszCII1df< z6=wneTq6V$_o$`iT3A?JS0aCW%(LHlyyK$)!TtyB?`}|3>aT0fQva|Oy^$&h1taoR zZ+V(ufMEtZ8*0%j>CgZ00Lyt+qC)ZLwi*KrN#!l>QC_!Xq_QZRSp>uoYu7s{ZnTtC z%pFXH)6=ubX=r$WpJKnW3dMeSpy&QG?^lo14Th#O7LP*AYN0EZjFmQ&kYL=6RKC;8 zjgKjiN!O|p5*%y}Q#qJ^(#R=Q#(sVX?_g(_(rruwog;g+$!0#40xd8i?;YqliX=XL z0sADhL2zb|A@o(clft0ZfVH(^L>ZjCxa?Yr)@)`6w}FR;SRXUr*N$(rn$^;Ba^Nwu zXro{(ZcUu_TtMIpFoWXJD{&YL3cBdS!Q7Lj47kAru?zzDbr$6iSSBUsN1C>PPAwTh+?n-`qFDkJ<_7!yW?mzNZuxRFTpqiO^OHJ1M zPkDy5NWB6gZx{%I2o;P@XrDfNCl2>Mh}p ze-2;Y_-V+l-U#S{qLS(j+M}#{*mi}Lm5ZHY4RokyyU*o~x?dR@{KA) z>vxPDkt;^(9HznsU#~PL@(U{g>@D0HFlrjvyFi11kKfEk=zHs5GE6Y7#wP}EY1cEs zfh_s78mILxr7+1RiMEf%LpOKmeB(*RgWQj)aeRq@iwGpZ0 zwbpFv#avMdlOEWE+}zQlA|k*Z4ak%}r}r!=B$}qU+r@3k7wwy5Fy(Pm?*s$}!hh`R zdsR*`c_LrE!mxB=0w5k?vB`3^a_4nE`uXYR*47$g->&2ux78(?y<`}W91Qip0air* zWRc^*V(&sKU4d%V=)zQeaPZFm!cG_uT6yi}=JxX^FCe7Y*hxBOEvPBJEeDa*%KrX| z8U4RumqCiG*VTYS8;JIAp$EOX#|VW*Tfy8~9e3?KqclbTpoKmNeKrE0^EWgW7jMz3 z^QxYE%+A2dTv;W^r#Tkk>X#^Nnq1T`fkC5ONK$zJ}wYhmcbs=~3V z)3{mO*Qb;lO#}|HpxYvxLPh{J`@e#$ov3=p<%s}CYgYC3n(e=TBPod#iNvG= zCcdwjZx>!HyO6Mf>dLdNJ)fNv&6m-Dcf%_-5*wn!I=+0mSg|rWqP}3)i}RNEhlfd0UrQC$qoCySL6CK%0g6*9wGeM%RO)No}jM z($bferjgj;9gi6G$YvzC7ea+AteZNmK^7(~jND(3h&qOJSZ!sk@`2HB-Roat5VKcT zhrwgTX?1jMswgidMR%Xh3M?88{X0F%-=>Ma@Z(_^KNNa#zcotcIMIk)Luty1dzg{L z9bPGK~gzDi;*?R@ zY?Z>{C^RfLiFg7r~+ZLYb#QCva?f2O40Qf&Ft=SqJx9s#%gpRBCy6^ zeY}}$f-CTD;^TKi$d<;=PE9Gr8a1K6--N}-S2^Mj7B;s4>x|g~3j+ul>=sQ-Vv|YB zCta#l=vPqY$oFQ|Yh1S6xl%Amr8_Bs8qmnCj&Evg{Dg+)4(bjwBikwp+^584*-JSK zbnPwU=r=j1mtA|1Uy(o#1JjC=s_!&IovC@CNq6Jqz?npaC{3-cWZ^AU#B?Msf*;*+5Pc0+9s>l2Z z2rur(Zhv@3#xkoLi83li*N6Q_xO0H&PS|4qBQXr-Ex=bxWHK-X&hj3K+*`4vTSn&( z@vvad-qY-V`kvr6hbOV5u<#2ZA-k452H4o%5&Gj zljX9pdcY%R9{Y3A_S%k=N&4bHDZWbS~;il-?yZ3lX<^uaaVbGnyj zdWV~{{uxoPA(w>eZG7~hIm~~c{f92ArZPTC8#I;$Tq`)o( z)B_jy_X;(9fp0;XZ1M07Z(#!#WCvi(m8^x2}E((x!?4G|}3e z4qQ!bhBo+qE!j$J>cXOaW~SK-bFV5GV!*gkPR>S_JLSdpep4r{`^)ZqSk(ysUTd z`SOIv>C8O$>TI3UeyE>c&xe3CvCDnTeW3*C=8>k{naLF)*Vz_f5F%4#Fas*o`_u%x zX!Z}(e$)W+<%lRQ)>|mBXrnj+#q9ptc1}S-TS0tmH-yM(*BQPAv@o%*6JR$?L8u>( z_g0_M)AtW3c%E|M62Aa3ZJALfNJbHXU&<|#SXl=bCqsGk8&+HL{jn|4gxQlqxOhF^ za0azDeWOw3*dIT9(lu&b(~Qq1_=KA}oE^}T9}dgP_z#!B18;Lm;EtmW>|Ib{)6(Fq z{9I^dPZnmyb_mb4y#Sdu9p6cA0IhcfNdscb@^XZvwB*!^tWZomu%{;giU1B386=L7%H1DK#x zNHPFmVc5;K+%KjjVe%mb4DR+#@)ax=cDBoV8?O_bGFcBVbvd=2P(PXpa z!gO2N3>W(!OCn^DyVLqlwJJRaXQT;NBH_ciP>ik3O$Vp_h>ss-q@9^Sax3`Nb$tzN zs{1ojo0>^*MS`&@-*2*Ys-she;W=9k3^PBWr=T)Jz8yeQe6 z3%=#%!(rd<;Wp0dcvXm^zlC8Mqa6@ADeiRM8NSqSp#Sn;5curLP;ncl+tpCcn~e0w4`f^3D~ zb8^C$mzMZEjsh?w#REVFEd^u&!6&QkIYmWZkqA#zu441KZC_&+Ou;cHbMW#*zr}-S zwHq6AvZ}P4j#m#Oo^`*3HSa&uV>g|6bD;UmK+*eK<;Gj}n=V8W3c{zS?=-uQ_e~(| zd9vJMVXi~*U=OvzBdkW;koDp|<|@Kv-VqJKo1HC5-nViW#zzfcdxUzFdz4)5aD_PH z!$a*CFDiA;NXFmauI|)p@(tWy69wu>bW{`tS&Lju);Fb;@~)ktA`B>DAr6 z@0fz7ii6;KLDbetMU5_oIzngHGZ_1YJ(=v3{5OTs{JH)*-)x-tD2kVy;75|jnwa-# z9;r;Fbvzx4Ksu;i8p zvZk)KW6jV?XZ?7SoGY&!>gR!#muP&nJ3%jB9<;zAILo$uHBm@QC4~x_p%9Q2=}$?JR)u9oEN&PQHi5}TjH|`KelBt@{S+7vl3nU zJ?eU;baGg7^7&i`X)z0lFJP%41}7aBb67#%Bxcb~Rn*W}iS`$Wx$#jmDm)w?oP*N* z(eI$$v6`r6bSl^{GyBavZp7!l)zZ=;{PJZ>{FQmd4s1?2Un?VoYHTe1J|$7#@cMg< zxM@B+5$jK1EoZ9IyCOHPKs=s!3=v}Ds&x~yfSg>YEHa;yTqCn4@c0Ca_@TxUpTcMH z@%>zHZ*N`+ZXX|^&Q6-I-qThQa~9z{bX@11rmX9eJW=KstF>`osIX$nc1{K7VNsKQ zS1`B?N+&D^Yqxj^frDke>#!DGD6GF4TP1P96SnIeg2%Qz8y3z0xzN)) zdG|>N7uWl|B6E@8$SA(GR*Tp5)HBMvu69Ak^VIOwtKif1WUmGKr2IRYwB+RXafoUt zc+3~x^CBH;1&bXyA35su6r1#C6sqGNp1Erwm!3PIh19zr^-5<$PVWX7>)9{vV2+7C zV-vl+sVMhZDw-baJ?eJw%|~k)4fR(t zWO~;unZZ?Y>|(fO5%KVQ3)CoyTnJ-ij2QsAF@y$Qd4bi`^bsnU2l3}`sj zRp*myO}37lB2L1hO3KO*EAz_0;!QN7yd3jMFzF9U5u|;!gmQ2y?W4pgm{`n|lO^%E zxb^CJK7DFpv>=gzcPKdf4;O&Kut{k%Et&kC4Q4JCVKlYd$L47$oCy-U26|^&;%=f6 z1D)}@kG%Ys3~^Gx3B5_l$=}kVdbBd2;-~k|Pd^|%6R$Bhw0wgR$2K^RM8H3jgL6U> zlS|H5eZ$`|>EB?-m=hSy@;f`*tPqrhobr(9Qmlmn1KF=C6gESlt(A zd$EuYsJI-K=4MxG?1mkgOU}=t{Z`m_K$qZ)fA{4^?vYiM!IaZxkUhXI~aP-sFv1?!Z$@8|>*891{Ixa9Kw1jbqn3#V4Wt| z6-mi=c?`OIozk5h-}+Z5bQ!rUdT;X4(*wHG;rkUY z^&t)-2Sy~N8zO8q3E$@-uBT5U6x)dc@+!8Rc^n*MmRO)4WMgM98JUk0RFaUAVr9KW zC)In$r%0YllZ~B-5QoThbH>Gt+6=OpFiGieLsYU+a<0&D0#|7qUZBIi&n|0+{k07=1toGX zl3r zPY5(oe*VGp>_qKE9lUf*Orl}3pTv8lL zzg_Vnf~DTw_1#>+Bb<-hz8}gRFZ{?_EJq^t`RJD|hG4dE_15zRTnuM8z%y_yY6N@~-)zwP7?!(}en@$k~+Gapx&zJlODe6+@O<|-vcxPL?`s= zdx6IuZ^1+OBP$bA(%MY9?O-8{L6y|{(BvuNzW1ZGY#I~nNviD@*v>V7{b~l&gWbUj%Y>6v zVIbYv4?k5NdZ3uWMe57*>&JM#KDCxtp3|%QdY)Q-^aKz=_}tU$Mo_CD913Qr8kA-g z`t7m?>F4P?o3mp3MB{163Qa*Q$4_j7ul9`aZOw03uuP`f7Sn+d$Rmyz9_uN3*KkLD z&+Z#22iXA#X|TpDCuchYgUNo)OyNB3Wk%P6{8eMXM2VSR!pvZ}`+NWQGyyrE3n^2qPjdH~oInN1 zW7ti=HsIUPh+`ONuF;b7F7FK9z<@6?3(NPbi1m8teSFQZV`A+o%CoaaM$2;R>K1^B zUZ9TOp~H~X|5b;9WJtur(l};yny{KlSZKE4+~{Sg_09O^B-SxrV&Z~xuQy1MIPBt9 zO;J>|iiaur`4&1l0T_538#p4Zdk=m!XNQy8Y6y2>Gvzinjg^@#tgmAuEJKp&>l>Z2 z?Z&+KNt*s+dD%`q+X|=jZl5}GK9)_Gs{eQpI_Mu3 zH&UAnBRs_vWgq+QtxT|Yx1-ZPUSqorhvm`hBk;$Vw?+bwz;%BW>M86ANw=dNDBZzv zKg_p+reVD%_<#E1B)2c1%Gqhh4@_o?kw~Y;Ap*vlY z>%+Q;kdRMa9?jm-OzZ9Au>F&>F|{Ri4+aKulr=le$j?F-H%>jy>b)2Bn)8H&%;x4| zopHb4xQ~pFuP}F)RArl-pEaYVpc7G5Sjf4Z9LVeIhr{RFbS>zG(jbPQx#MA5%wllx z0*wED8UD)bPB@y~Qu2L1G?qsI+m}Jz$EYYEy2Y(Gz~qaVmv42GoySi21$;Uz?5cO5*f;3%Hplr7ROeigeRUkkO2dPN(*>sd5VJ!7-08qNh* z$XLe3<1zR4X0>@d+kO>=gd9VqJ2bTRL<3F=H8m@!THTJ>q(stvmhIVB#%~@yC9f4P z2_4WUd(Odqtog$Jm4HBffh7(pDTwxn5;@Me1p~~5Q46<2X+}aG-Q_k^+<}n>>j3NZ zJ16tt9X4EeI?1*W%dC0(*6oAB6`4`Ulc6X!Y@+*|uKE6F@$bg7=Fw3CJ3BkKUoMsb z;~e&j3{4W(P!5Ps?Z3BxU>EY>H*X@^MB64N>cj5dIGJ5|grkam597fDQym=+-KLhX zzP^o(BJjqb)hyQN6DXsK!9uz5mr1}{JRP=Aq4DR=fW@pB-DYWWg0JY8%X?wn#6I+w zJ$YDMGU;;sL;izG%;9=r(3gFbwrh`lVa}sn|09OWLP1>oQwIY!|KwrV`wJf4#YIL+ zLUOV(?K3#2q-Is}{_`U|zHx;<%u6U9*8Jef2alLKzEN}G;ehgJg~F0rhkbJx+>|OE z%r3T?3;&Vg(j@k7ZP~kguDB>0XkVBv$a)(;-x$mgHL|epyG$gNlUk4gx`tBf@)t|5 z^sbAszZw|N|R^mPN0-T{2#`?0xHXGTN@h%6r`m@kZzfUGHbIu+A{l*@9>_FgM?~1ve`Gjs=QAyGKqTOnI zCP|LYmC`pS^NqR^^?-TC`5S|olKC;J$vL6Y&5D<}uy}*@H?7dc{&FkD>CxKa`um+$ zOv=cJNR|kJqf7^=5|X82D@gC@tj9gv|4||vtCyGr=(Z1!UqnpO*Xcm#S*Sk>ejCP}Opb;)8=XGieq14z{Qjv~8vj zHLsouIb;f+C^=3zCl?n7X6HA$Y;jXkCg;k`%}9prE_b|5GxlhQLBBpl6mzd?(wpj(PrGUKuwAMjE!lat@TN?%N|@0k?%r#+%n&TxD7Vj1^Bm*kFy-uJ9Bt z*9|`A_F>$)UyjE%csP1eTHUuov}`_HHaa=9U}9vHl~d5r;P5b+spZ?{_J~Twj6~_^ zS>6`?;G$0nBvjYOuTd~&GQAvsHR2p%;OrE`y90h>>4{{)iGlI}aYDgyjEg-zlg*~n zJVhDEXV4PKZ>9nbOs$uXNOY$zIqx|+JJ;Q>MY!(WB+awjn*F|r*jpV1lj#Cj8;P!s z$ng#DZ7(#1!p$u?>FX)WI&O~h!BV{{RuOKg!_sQBV~j>=!OA=|Tr>EbvwS_IbvrvV zk$(wWRx0oO8VF41i2cPL+LRRBcee_M^PR<&l&H=MDLA=B&EGe)xu5lXximR$1@^th z;QDeZHsh}K!KYvqb_fDp_M+m}{^Cmt@|A%SDk@IzQio0x%i*2Q%#_DY&3v+24o`d= zjCXEyG-*|gOnTFcwQiL7$bP;7qpUd%pH*B%*|uZFYUL2^e$uYBL7RfSJS&lb&I=u@ zsaa`{b{EaD_7B)EUc3NRCJxSQv7=)(TF^+7rhx4?))9kkupN*~4oEN+!@(7rx5j0=8fo*r$i3_wl+HG5t(sQ_A`?EV*Xd-hCrw8c znup~MAjcoJ#U^l=N3yt>W9ujWv`7*hZJxM7uoBWo_r5 zIP6Eh@`pN$j(mKsmo5o=Jn73AK}ALF>dKUgd~k)4$^3IoK=cp`t0q+9d$i&=M8MsI z&-(DVkoN4-+vj<!aEzl)Ey|7OTc@ac>ZpVSN#>mX@k{QAUG*euiC9d6P_PKGR zu1?HYv9no98V^;>Sya^H0~RE+y?dw7M$E@<;a8RUNP-zdD77~&n~>jSAXxP0PZ~-% znOj$Qw%1OCY$E@JsJ&947?tG#J=X(P)_MdXM=$?Qr|9X$oSdAu*U*X`m#&`usI1g~ z`gDnlu;5ev4@lzqWy&#OQnO4{$H~QG{@&-?_d~?3#fQjX{m!USR%xEL(j1Wii%T2V zVyt*(f?0PvJSdlMc!NTtqIrF54`0`ib;R~3#CK);!s@|Ts)0qiOtf5GEwn-3MrJ0q zJQVg|sK#}<=xI|ZBluIs8{Mqe$quF0WNCHVP8(R=*vJmRT+UwFy=eyok!lUiA_up~ zCcR}g?KO`OK}6u0%hIki<=jU9gtEn$fwHoyR;eZd0l`Y$AiwKj5jej79K6TKs8~er z@x)vP*sX4Mx@LUkydakL{o(yd9GQ!YhnVvN-IG@&%8&0uhp?2_=!aEWIwY!)kyaaH zWeixpaaD6kOC8EXdx~N6tZHl?{;1fXAG*;;Q-v*#TFsa=T)mwS`nO59=em-uXIt;^ zn0E&!q4@e1-9-m8^k2{fyY3>YJbq) zsVivWRGgfcQxo6bsJcd|tQtT?O?`5*odWp)mNZrwOtuRL+I%0sy_J; z8xqo@nP!*?mZlc_nSelEz2*fs{n0W@`}Og7H3kkQBS$l{l~r|T%-aRo1D`*o3hfJU zkHdx}8OpFYlsVS%5!3AyMTcEfU%&ideWC_7H@NS(^EetTb}vj!XhX$nYUc=u8rFhF@&H4#y?u| ziwyh2Hx!}`sAvkrk(E_cM#_)IVy(f(R|oA1-7R3C`Tx)XKYH;ZuH(7Nva(^gtu*XB zCE~FYg3O$GEL_~9`{kI0f&27UR#tDXqPcCazm9)B(W1&R+qzR>NALRXs5x0S2F&Fi zN&4bF4jhQ7t8+yc(iIz8=`U6vzKBI_HbV!Z-U_D^nwbVjPO!xgV(1!SH%0Hq|C#WJ z3{h-dW@aWv>H)m{Z*o;S*>DD|%JL^>juDYQsBxY385ca{t0t(1C%L`*#=zimQE|(B zM*^jg(Lg6s*n%9`Xzy%qQ&~}EH9vIVnRpKnf&aQUL?vKhElUjI6B52KH+X1>^~eBg zLH!=(OW*o0(vnAmj?Q8s}?g*~H_$ASbf)G%*$BrySA>#J^8BB$d|=hL~g?k&#gca9Xr# zuMGA;7gn_js_nwXK1@$H*ZGff?CHCVWU8Ae(f{km#ex*6Qn z{sT&;F!%#=`*LwfS8IU&nUbQ;u);}MUA<*$1DYM4HI<=rG&GzZEfbl5s6h@!{~{&P zJ!eXDl7qE7=FF3Uc})hhILPtFa_Rv@Jn;WFrx&KcyweBO?_kZ+=UHcMtdFqzEAFK9 zi2FCaYV=raZMZ|^8Cd+Q)hS8?lD~Wjev96D|4hVrk@z}?NH0oc1ZhH2Qi~Q74Nd<( z3-iwQ*P`f`wbx^dfYa^K4u=<{18+o-Sz#eBB^OCfU#|1!!7<~$PVY?lXTyZ?Lcd$b zk_}A(petVSYH_y&SOTyD`?J##46@UKtctZV#sh$>F(`) zVf6OvBYA1o7LmxGBIe8`=26XRdU2L&-}^cy6C11z`Yq>}3iCA(L- zYke=^1hI?Y!j@6$cA}!LPXD$_5V^6j2_zVb&lQMy^UmOq!pCRq70*adO`owUhJEM! z$Q*>^RJBsOPiHmSGO_&du9Q?%f}a*9Xrv(?|Fh=O;TUs_Pmh7E4vr&PD})YOESX1yI> zm&^0$_H=_bw`-j!C1hoFvwa%B|HZ=v)t*$DY&8HXC6-f>l|Qm|S4j9>)}bvb1SyRR zZ33|>$|@)y@wxSspdu|V<6~jbV2Wr)3+e!TzQRo-MuT`|h*nhGqPmxb34{x(1?0y& z%d#ByVFz2T#cCNrPf7ke>L(&EZzgGa?x2xeD&wt$=+)g-QyuE~@4(}}SB;Uev8Tsn z$7X2X4wo6UaOcdfYeC6eThh=5f>OR=Va0bwM#uIKw!U~u_7mOpW8UZIvArlD$@3UjG4q}aG-RC<$sYmZS@w-=bV=@(| z-@S*s@a^}ZbpD#*AwAj?D}(u4dwbqWg<`Urnw+BDxhiFno^e7 zH|iJM{A;}&qvCbXA)`PZfn6*!pSk44U1iy#AHKNZQL3Pd1w#LBpa6r#NXe8*{ei7t z9zF&UHmhm4v9C#y6FnHY>>d|C?+|s@5zq^TGgVJtKf4c?QKLVsYz~^L&&J@z)9)5% zJnket|6yakb~k9oz8M^zRq;6;JItmkVoXeoQQZdh4(2HsnfR5vs?hXo zdNy1ftHyJ7KHlkjp?;6(f|;*FsW%90!ss+gjODiH+F}nf>+3CSEiHl1&E-U=?6ZU=%r{21F8E4_u0p63PBd!n}7-t0H3g`P%ALJ6|8)-S0yf(J(T` z&d&C}A$YiyARq-73`=is^IQWi=t#4hD5&Y^$oB{!iX-NxPL5LKc6MS@O!i+VC4JR7 zn;*M)3whsak{*sXQ!{b!72KVbesjwc0?$pgDsQ;*RUzuHjb9s|nmXFluj@7r5fZv6 z{Vrnvpf$`!TPof=fY6g`0)u&XGb`Ye_j88u(Mv4j{WTWqrbXPd@U-`ExWSrpDgx?n#bTMe$Of zxw|%60ega}nV9P*EjKNANjA2n5J~&!m&FVrAJWtPV*>-hi%H>h z_2dXzS}jd&P+>^Fi3$GWFFDWSMXuzQXJ)#8B)vbcv1;q`jgmcjgY<<7d4uL(g>(h|i1sbGgC zmKVJ-ojK0ImtUXtEr+93GuWA4``WLK4aIyYGMozlt3LJ9savNOBNeWOCnfKv6BAht z+q4y1KlAMIpPj5iiD@<#6B6RQd+fM;_|_}XZh4TazGe#AG#nynO)e)lO5K)rS11Ex z>&ymS?s6$Ve!N&2|F(Z;=PAL}n|L2S=gn<5hPZ9HbY*^()1)*Bqm#*T__jo9b6V|b zYob6R%QaZvV06?P-7k6^IDh3d&dsm9d|hB!6*}m}Zc7H^n@RUnO&_{bSMEPMqM0($ zHn3b@*S_i1)!kh?@$x1Sm#Mb-YmSckhWV9J`+HqT_PG7p^lr6(G+5(uZB`YuU1Ibli3F6Y(Hw2B#8eStBYt7}Uf z4()eAp~+C99!jtAM*ZxhIpPNju91leZNA0Y!i44Ar~s|9r>Li}7pna8Cy-US(pAsN zYs$7`n`75FN$%@aLM*i9;$mZCgEc*qTw0`8-tvXPFOz2TO!&MqtE;WMa_#bYN{4Co zm6If-Dan;ozqLtEl-$t@>nOG+x9ZD<%DFkC6{=-gq;6p)C1OA+ZFt0#DkN-c%hS}D zZym$5F_Rj0KB5)Ne0_4@Qw6?T&Z~snU@~~h%RtI}&xQVMf>m$oJ?Uo)kBujNWPWt9 z^mltX;a)8j%gsj3#YN5Kc-UQr zSjW6o7ihh%%H-b8h}d2p=!#R!e26%%)?%W{2+5bkp;YGW-a%jNJcvbnRaAI(So&2= z&ryhgU5tmje{V?~{WhuPk>rKCut7!qvBYUtfT|=)?d-Z&m7b}TfZqsg7phs}`DLHW zd5qh({7{cE*S}4HX~@JHV>HFR1ddGywWQt0$GDAk8?lkzvjp1k(_G{x1EwyE*LzBa z-u1W`+BrCWYmo4_(7%FbY_6>ap1-n90xUMayy-d^+riT2B@R531dl*Q)1XGZ7#`cl zP+O0e6ow`Hr?;dwH`{X!AY!@owY9TtI_~)_byx7pxPh4HSaZ^Ay7S>a$&c4!T zmpg3_myZ+*U+a1IPE6b|IrN7Fsn$a9-jyp)qu9-TeCon18)2D*yH~?H#t;jYjV$!_ z^NOu3>T^Bp2E%^AZ>wF&yFNbjMi?%0|8O|CtKZ&qcWcK4`V_~X#(mZ+DMHU3;2%Zz;~&$qqfJikQd zF`Qg_xPLU!)au{#SMP7S2Dg9Iint4>2>_OkUyfT=SW0EREAjF+MIvD zYzoM>@H<}D!?}$RN_BO$EMKfCDx5{9!Tb<2BcpLm5%Ud;$3|c9_G-if{PGEW9m>e4 z#b+IlT)LQEiBt}p_cMNfs98g_3(d!Hii#*50h^e3$tiKP>z|vOkfGg8HI(Rj)Ngt- zs%0RgtgW~AYUwI>i~dn7k?h&=R#);Nz*`>aXD-{l-4Y?U3(T9Gjtb)ACm<_Od%`4Z zsVYAH+(BhKrJ^8Xb#`S4(1PR&nO+9`9EXO*1+)I6{*5WySpH8RKQ7BWzWu4e-DbIe zV>h}~jUmjy@vwG<@d}>i_DX$U^{~rUXwjSMH$e2Np8V3kUxZQPH8oalxl|C*$iz)= z*R!%IK1dQPz%e~NyE>c;16A6Q5VE^6^^$XDW|^I&e4h1@*~e5qUkudjo5xUga00?yZYMvF7>N? zu;HoOl#b)JNH@FS(v7oo_!ILDzMi+JJmBJz(aL`8a_oX31hIQE0hn1!Uny!LqoQ_K z#?JT!5W?3!MVPamnsuC;g9deUn6j&C%C`0BbKAM*`PNk{`QUYrQlgRc2)P^ z^T^F@i~War#O&htq@)GDzNLj&_s+)Tz~sJw5uOAccB|sy%u>c@!>7+)ZHSJ!xtz=% z5X*4oSnVwO_&m0?Wn02fST#5AFT8OB1vXPO6ttwS$xZ#H0x%7}PibOoeCXihtCm5u zHvXhm$MNV;LPE0TrEN>Ii)b>o#|OBj1$%<^D7(3AKMiGHfGOb*QT4Wq0to*oc1rW1 zx~UBje#Yv#r-?~lH#KWD<(`YOukzThSw^s28S3LT+nkOrwae5j4Ygx@I8=$wsrS3> z2P3+V?~;LeTuI9!F8jHmx!Di-R(-{BFs4=2-mN;tNuio*;GZmUn?*wJZhY?I@BZVj z7eSMyc16(SjoEjkN0g(U4jP_kPvgazRrUBL3UHNow#X7Z;nx0O-5chi?AjPG>`D50iYd%(tUBKOK z<rHi2exwt#FgfS$`#7uS{?)~|VUg`ba$SjvAC0eeB+(p3j7{F8< z6$ORSFigO~L9^5HFN|3AN>l9{`2PAFy#?5J?)+}TrMIJdk{tEdGTP5P9_%)5?NXz7 z`q4PL27g1rBkG8l;@Of({#>4ZMcYGCslmP*JIA5 zVvF+XRplzB={JS><&yB36ga!Mq)lCRUM_)`2EECwK3s;F*jbhc?msHR%3ELWH`RjQ zKt6D(Uc2A9rimNYBrPj@bbOGTcnQP5X+|c7g?Y`DSZMwq{X5;j#5V_GjwZrz1*)W? zqPltKfx}TidWLyd0Fi7;N=j7*3D4mWjK;eA!~{n7&M_Xp+wm*(&i3|k#J9M(yJTc< z-2rEJWn?6=FN^^5V6_~Zr(

0bf6_Z)u|EQq#~bp$So0)s%qit?)Iu;5{O(Pg%gB zdyVen?TvQxW>0&2nd8*zc$M&Z6O6>->A1%7BuvVa6CCn2p2Y9pOU#__*#%`8Hv>TK z*`!;fX0Fw~2~yXQ(p?I2Q{7xHz~a{(cyk<(-~J~spV43{CxY^O&h7{0=|ym|?Y#N& zMF$Y5OyZV8q-$3;kL(8Gz7>D}{$i=17WgZ@rg{3r0&a=%@hO?f;2ChlfI>Pw{Xhcp zmuT4^jukc$L`SL~RK1?VLVD5J*_mtD0$9jBa?S@F9DT0un&O48p$kDT5y0eq0pMv! zh!5U@5JMs4A*B|Bu&Jn&kiE4ZP0i6$W+trPP5R8CvkD};QH?F&P5~pjxrIJMLlT@t zHS%m)7GDAf%xonfG*n@LYvAwxCvh$xusi1G=l$@_!eV0s)f0^(vv?_3xAZ~P{%HL% z5OmZ_ZO~3^dw{T!n>!ZtLm1Bmpgu6V$Vs}u%^kh7Qz{!yb?6@^^X1QK7C=>)9BCU3V;|1;fESl7Yc6M+M+>=6nHccRE>t#;){DK2+x@F z3V|xO7^~?4at<6^Ts zqK4<>Jb7_=V4Z@s14Kh|bAR{!_19^pJeZi6?g+SU(!74%M9k;*9z7?g;Qa_8hr@b1 z^a?yYia3-r^B?3xT7Z#5y;X6&XYZKA&6SNj*R?FK_OlS#g+onFN?X@XSY+fK)Sy& zL}w9OEPj)ut7iplFgQmoO{Wj3Tu)XVMv;j{`L`)=^P7DW_n3W4_F+AUoy?#$+;yvW z2UyCwQxy+i(`J1_eyvk2@0vdM(bSL10ORm9#(dGJ@$k`H<2|W6%HQ(Oqh&B7PYA1|-I?Esc2bj(8>Mik^EOBEU4)_Sb$ zW5d19&j=x@iV=yRMA=+R$2|2MvM8n5=csHi90AWZFwzqb1W^eyc#{peBCmp?~! z9+wUYcaYPwzd8W%I!Y+ zVsdwBYV);Clu>bUivWLr_Z=GRn9ROf?^YOg5{_0kT`5r65h=_{_bz>k*DkeR0YFV1 z8|UZLMlJW%r^C88jESQjVO$cLht6B7VkTrQoUKepq{tF6LrerDFc6q;^9-)iu$3n#Ts zx5rZeMZ;pOToiev$WmQFVYjY@06D%{krCw9*HHI8(@?5wBOg_Jh$sF?w&r)=Fx8)5 zTp6+hFg@e?t(vGfe)4S-zGM$w5kB7r7QvMvzL9$g*n5xUxGl`96gkyjsyh-^zL_Kl;t@nIY(ztvx$AD9Cdw=$zuk5sbPSJQddF_H zU-$Tc7<+T=)>{3K`q|mpETtb%;6NWC)$R#4gE4F8yB|8yoR(=7rRjCHu9Wrn~HR;OC+I5Ta+d3^z9aCzm<#KV7*CTl;!BhDAhp zjkbXEE@gpw>+tXwARCfRT{%_vxwP}``WV&ffMfTy-3jEj1(1HY>>T!N>R$jy$#?I{ z)xVZ@paw^VCCm0Ps2}aD2*93oo>PTqo|2R_v;3eqJ3HtHP2JDmJw-#{q=eF=0nenh ztqpexPsB2iI&px}2M=8cc>_!*kB;{M%g0+A$HT~esG$*)Wsrm9p$dV%peG7rP|nC4 z8>y^BANwMMpbz>rQ5HIXSf0gjl1gZId8zO}-`*d7S`_V?-i02(6 zI@Z-ie@`Vl;6c*CECnIWfU|Fd{U8qEMadu+@a!NJey3g@8E`I*8EJ05I8;2G6Y@+# z2lGf;?Agap@{?1AKuKbj*lj*F_47X>CpTrJqf^RiZ_{mU9+M&7+uV-f>`*RBgxIc7 z`s?m|M_nx=`ll|M3}GgE-Cm7)87ldQrzg%Dqr}&4QryC0$|@^E2J zIibvfQS|xoUht=QqeyUz8%D=ZkBb{$nwwde>8L1wX0uJ<(}Pw$j{iS<4(lXD1x6FM z*}cwdMIj-zvkR9HOi&vd(S~;l{2-Y;Kk9AX+t1e~Nv42Vs7&kB1}&}}G1I9 zpSMWf9hcH71mS%c%p{}nbggkc8a+_}Ks+XgXOQiEzv&jia(Wm%S&!PLi zUQGM2wfrzYrL<7YOcHR}M|U2{HOx%pYpnkCK{U!~<5o3DvEs~vLW6{nEu-!n+_F5s z9J#ThUJaq9C!1y2nCXtI{4h}?uBACUt#@{(M5l35A1DO(*6#Cc1$=-oZY9IH4&JOl zNEiC6bLz9>&45*vCunR;9at}?*oXCJ4SI9GgWWAB2dK2j#DRO8`eK6P&wy?XC8cKP zJsfL^>ylcy>$nA?oZNcl=ZDz{9*>WiQ@0oE?RPX*4_aEhV5if7w4}kGDvuw(s6|fg z?nW6HSd&sIdD4^PP5un!Ma?coYAT9b?fvNQD#!39p86UFiNu%(b@sCIEMHEs4pnS;7PN=nL@;`M3n8U1Mc`6O*Gc+}KDir*p7 zc0Miwyv(O|pv^S|);|c4!^1B3Pk#1iCsNA1=lvYS z^gJj5u9&0nt|75TRoPzzdB87^&yF|AV$7}opkdo}LgZd|P8SzMHvO~B>t}XKT z63&(}DS(Vf;dnMWIH(f&@O;32eFTu0L%kW*OTe2J9ek`$eHB7`VX?RW9x-~(U-bdE z7itDtpde};uV!Wg8pc~Ud=UXk9oxs@|EY2KbN*wL>td$pGO@FqvgzpqmkcwyE>pd* z4kQHH!qP^)Qrfmk4yAT0$0x4$deb|)`jX9k4eXJ{y|U~DYdEUHJoX(J~-^hQ`{p{)42*W=yMdz*aX!@^n%FSAI_AamPNcV&SRrj=~? z`r_jElcPP?Q@UM?G9)8wXn6$@DgD71_6L~u1rkq7ZFf|h)YRyX$+s>6v-iWNlesA} z292^TM#r`_M08Tc4DAgxG-s6#I z{|z&p|M&6w-=4UCf6e;8Ti@=d{K01a`jX>+_ri61ktIc^4TlE+CqQKYI=ea$CUXc( zB-H`8U|#u2l8)K7_e#b+Q%xd@yTviGDY4i1W*$Fx;2&}*eCNu&1w%E8*}pi&p*DBN z4L2Yz$)A~ESj`a3X~a9TAB}Q2Ax#wj>kk=avm>Nj;JP+VEu(`CLjS=M`s{Dt)(O0J zG2^*zn0<@STv{om1EpIRIUCy!&hRLXvoyvQd>2(UGgT?Zzn%!G5`s4tkjMq)<#(DF zwt@pL9*Y1`CMYDtkO?pN=5*=0Ke3=g#=jUhDpF`ArSdt9?U_@R#KjF))hs~#2(c+Lc0xkKQE7cDkt(?cE%YzEhxtR@K`?yp(?Vsw=}6yN6hBup{QktXjC<>TJ_(0Nbe+sLO->HmL2}|iMP+5K zPJ`N+rW63Fc-<$?)iQ9y`c;;4n(xI#V`G0V-COk|W`$O5p93FUy?|+Z164&qP3>b; zRIX9S@kYQDRiu%*dCU?Q50jBHw58o1o_6Z{;?iwTSrzjVGwyW$0P^7^+qHBhV)P=v znhDUWIygC+4i{Z0ZPiTs@ zJeI&wJV>ZLyEk><;k^q?VrT*@D<~8&ay8n0ls7Y@7iF3vs+YEp4tCpl496UR0IDhx z2nf|?-Abx)D1~SEkn~XiiJMJsJAkPXt9*9{IFF{58XBWeF@vvFUH<^G6zc1zchkIH zp1&S#VJxEB_iU^SiFsvZra+QD3J&Qv-35%WZXgi+d_x-ss;4$OGc~FVIYs-A%_PXn zJtX2{{Po7LA%@cg-bxU+fP1W`?=g@Qa&kuL)TS-?tcn^Qi6}@?KDSKvYWU~7k>00) zw2I$X0%&CcZxoCdfOFN=)dhiwW#zM^9?gHjU>pyY{YM~E31SI}iuu2T1`#>_I%f3b zqz?6+hGv%t0Ar~Qd;wk%qJrCP2!(7zt6M$75Hr`I;~9`1L0E@O3Y1Y`tmYM|{2RS; z-;Plh1kbkJNlO4d0-HOlrMOiFz`3z{9c)O&@VyuQ7rhetqUb_kvFaRb2W;fXC}kL5 z1CEVIRu+wRsXGD_8sN$B9~{m1ao=925m!?)o2T(X)CK!PU4{3|FvgI4qx&C3Q@xY> z!bI$&p(34TV&)2n#ubUj3nk3h6lej;3SH|ZCP@-a2o|St~g56X-dOC~M(-t~% z{xP@4dB{gufIjwIE@Aw((tqPNx=PcpXq2Pg#ylNTpa#DHL~nb$skym9ZW9|@{``ZE zln@!|ma(3m9$?xb#{4;eyXO{J$Xa)X6&yK;IUh-MKtY$s8+f~ z8hjfxIhI2YXcq2EUEDFn;!;#aY7&#Uo(d@OaUUX!p1aVOjjy6N;a{iIUCX=zhDpdl z?doIzs}^48)4mN|>i!@9R8fakY2Edho}Muk8_ZHl152HrK6f4KelVG7X>b6`^31Vv zK&$%urGZCr<;npNr;^OS{8tV59ejMU|DOolD*Mj9?}XXY=SMSCZ!jom$xQV0ct?Ji z&3uPA@I_=f8uQ-+69M2PfY_#^gdwqKr?BBtQ+s(fp?_6G+W8pgjUWxG5qCG}luG&Z z$yQWy6zs z-teDS{3<;buyl4kJLLnF^QCJc*HH0b#@5YPUGX{TbKHD^AQWZN8y+_J2I_$KK6j;3 zivDcp|IP_y5DeG2&@K0d;`PK=V*W$c5QE2nh8NV<0p-Y{Wa;Dk9TPr-w)}h|dUISA zrX>~1a=qW1cM>M6=f5!tV}B3W>h@jd2R8{Hxou8cqHzCX#4u2a11T5q+W#TJMMtW% zkB6rQ!#8KwjY&_}M(1<2?hgI$aej2JqPusm`mD%hC$)QadU<{gq19|ux|T@HP0a7| z^0+p;zr`}Wy|-_Z&o8q4t@@KES@}H;nYZ6Q0`nLoFf0L9A6%BPMMON{q|PcSp#jl- z&`04KFV)A~_kR75b=}SN@CnwokTcoqw}=PVCYXiakmhso63YV8)cMqfnjtJICT16I zkP_EAg|2vBp?!0SuxPE1IWJf}9sgFp2`IP;{Y&UW1S6EmRWV`wa}6MLS3w3$nrb^;F}@ z?dee(R#u(wC&+-8_lP@`y|KA_xVc;H{E^@lvnIDma{J)?1Y4FbZ*l?n!N-T)CcQD_ zYYd;gSm(nFNP{tJIuUi z6M|FoKe`Ybn^3O-gViEO!^HSSOC+1&_B=Ck{E)pl$Y0c(X?i<4B6#|Seshv9H7<8<{0=@BxbF9DX037arQkWQ17N5yGL<7f!Cl@L4|<6T5n} zS>yE>m?jhNkl&IwWx>3B^l1M5L@07RIN_0itzhlOn5TG%j?*QiLBfA@+hysQw0=PC zKX>YWi6X2C4OIo=(Mp~BMm;>tOxX=*zN0S-kJnTSfvt|c=@=$xRfa#CrX54$B#chI zyHFF6Kuvjkj9Y8!^Yf?E7U)~Ruc)M?MH5huj5^w?drO4Z(Eu^yv^PRSwaO+^(}9Q&US{3RgdTW*Ckdf+)us(gn4wQv2zq3?7GQC^BEX2`cHdb%{*i^EQZ`DpBpzP z(6xT7b6-Kf=>fK76W_c)@n!$SeD`|iIa1^JW^FVVay)8km?600vn^?+qMo&Afii$BzdG9D`H5@omYaQru4$6(6gVq@-MT zR@c;w4abW;SkwnrOkh3h1178ivg}36f5%4kM&iFk7%*Y=OlEx=h6f!$*YXW_h`xv% zHU5&*BY}cN3oIQNWRsKesvjCxHK$}|YG&W7tjsD5>*I}tm#?H}`0MeUum1D#AWWNz zsUO@v%E|~!JO69FB~1^z(66G&hbYFktGg3Ax_6l;5YfpX!hv2W@E?JRz)QR~)8zN6 zGRbLV8>U|^8Y3rp!=RHM93VcAy%OpJhRZC=^+(84>;DaEY%J-@5j*He*>ztYF;v1- z#zh(r5%Pa!lbB#beZ!89O2KqK0~-TfB+W!LJ3;Bm!^=OgMW160WvD-=>+c)Q>Ub_9 zB+gCxVfqMzLlcK3!a7@gkp_C zOKSp3VUwlZ`1mMRR)I3tBMSc0y9gIV#R*)yvUSCk(iQ;-{T4kG5)!MJtF+XcDgC$v z8&YwUuXKE4YgH_G zj@J=~-a;58zvWQ&L7D(+?NEBv#)+5xrtDEIv!^^oZ_L>voPO0;hn23Xkfe@oB{p$7LQHoAEFRGaW8rn_S4H#2l$_ z`Ml6JW0jsZsH&>Wh^9td%4m={lm+1{&W?|Tu6zzH~2zrd8$nhj7SI!=bz${UT<74iyv^>(?UB*1l9{gh3kLLo# z8A0FL1N(a(R@T;a{%0diCO(B>vet9wgGS1HxVwa%V`&}6FcQgw;emc=J23(l%PiFwnDxi2{P6sTGXQ%yPnZd>nB9oGO z9v~K`1Iuxu!o&K=`S?y+qe}^S+#U!B$SNwfb#)CTX}VYCo3iuRX*SO^c_$^^r>LWV zgNx6}W#GaUZhDX=_M3S59M{3&eiBSy_g0Exin_=Iba6A7zSTmcFSC-LWc^#M1xW*O#(Q>=%pPG}YB zL+ldIUpKjTqDkS)`s*I(FEkuD&R5&^)OEC>v~S;vSOJAr#z%UGYPl26%WS$7I?~~M z;V$=~EacUHBP#9g>A9c<;39DdB|GXfRaF9fd~50z&~$XL*c#VuFh(N;WaB0FOI3)_`l*v*>%#bWjI;X^?|Kz_Uwr?L6qnEi zOpbutvkHu-2Oe9|Jm3grK`MZMGb}3HAv-JUiLh`2v+lb;lbjxJRN4>z`9zC_RRFr4 zw`_^(G*>Sp!LbTy)yZGKg4zu9)P)vf+p9fZ2r*P72qrL3Na$i}Z3=6I>oc^+$1^c5 zy%H9$he{OgyB8|UicuzS)@~_kU8wADxQqlAUI~~}u^pY=ljmOuG#|}5H=uyQA4Yn4 z@EF?jH{GENpr@zjWMltWS{kyGj#o2NE7>Da?+E)dJwT)*M@IH7Y|kNUYjE;?Y>&gzzRxnd46p2k{VBQa^P8=@znE zvE+yFj12P{$!#>$3vpRlf)F$ceq>;d=lVH%@e?)mL0UqWn4W*IVgLK#i#YVk`A!ES zdzRpx1Vay@K6eqk=f9_fI=!!|V$+mm)cIB;^ljh?)pHv4uijzaj=UOX`fL*yHxQmh zwuB5`_iLWSJ)x40LOP#?T~nJyNP-Dxb77obH5ZU>cvhc{JWlASy*9bAANJm5YhP2m z&>|3z*P)^IdI-$L0n^mkJzgqrD?PBIUqYT#pk>eY< zkDuxwa~~_VUIJOz9A+ztkHGxuDszwhou}9^Xl|Ug`E)X7($)1xHkJ#5ii(Pol2JnO zdWP<|3B*-ZuOV4ko#sjc^M(X~DWvgQZ$w&p+NCf$ptDI16|8LUpjEIi2Zb@^=2Bz% z1Qgs9h~4zrbaPc#JKVuNm3{c|;dK5peJV3^Q})uhyRky>T2Ym9^aeI`DL*@H^E#{} z9TX6pKQG;I$=U`7_4$Yk8#S|7s;ImmF{BJ~*RtmNd{#EQapR_@R8%%;4<*$P6ZwOX zLOo9cu_-I)_)H5}oE(8Y#6X^74ZMNV;o_rGd7AXKG5WFRD8@1<5|>T{BVV+xzY0@v zC=WQQIrg5MV2%&W4Ow>=*mhdF^|QafwxMBvxR@2R+sCZ1Ca&YQMny!xLSn{4joK~f zvmkdpRcm(N+nQ^G_!YFNJPV72v^1C1V`YZDCJwVM)=s|i;Y>jIkbuC@Vz703(l-X< z<)W{1%nFQkxa=QKLF5<#N9NGL02nh|6qHTY02&T}Uf7p`GQN`_e#`qiS=$Ghu-EQu zxS>oO+G}HoBiH6{aFt!x1Yf-V!GQ~cj*b!iZh|mA3F8tDA(-bQg<{nF74!P_InaR$ zRddbpyDlO2#>R3EycmBjz6ck+a#3(5)z2QKFj74818X~ua#1{Brz<@7U>z2i7!JPY zB_L&^zk)P8EXHwUj)%)Yu{?lzvZtARr()-%bSCPy`OT-P5>8X9i4UL8@+5Zn^|;Cj z$f;f?k0=otoz^VgV{R?&azoPU2_1Dqh^u!fsdwxXd>u;UrF1^q9m1=U6cznpAT^t8 zKi9gFxBXy(6$qm8a&qA5Gg0lib)0C&#u3!DqAkO^|Qmk8dsNQZlRCM5_b z@Bfl?w$RgUT3O#B@jlPlVq+s)xNyER!uELhP@-H)O;y!-W^v|~sN|czbU0?o9Y^hL zokE}C5?twG97EF2vqm3be=1)BF+N7&cK;o-e++av>;a zXt!1b4()~WflZIuSrVR{+*L2Po9SM_1Z;tU;iSC$>!Z8{)?Y&k2c;2Jr&|*R^@7TB zMfjzoTR`ZAc0QPnd##r)Z=Q|meY79{ygRL}hI4ch{N5+oqWj$rL+O^EP?Ab4GWN$H zc^%{c=x*WXQgTN{fJU50|7SOUc{Er%i1`y|GqShVCYk}!ZI+(2v61lsTmIp;9Y2(h zo%yOr;{fGQ6?WVr;R73%o<0tx9EX&Mh~k%zPp)y6`V*e)Hzs^0gn0R$FP^>!x7^zr z9tFNfc8}T=law>r)LVm#%c{3`fbUQn;^*%=*wXUp^sX7Se?mTeS{lq>@>|F~pe3sj z2Vb;pg!@@cbhP?-@C=&pb;>6yDtC(cj=_Nnsl73~tJ?Fz&!1ZpOc?k%U8`e4fAsi2j&J=p%c{M+H}KsF$BWAAG0r{vmmxnJ6!W$Q&O zHd;6;ZWvbn?4$8W%gbO-0|6we?}sn9Mq(YYjUQV@MsNQ=y50gTs;F%j9z{h3l}1{+ zLFq;T3F$^sy1N@eQfZI|>F#a;>Fx%R?v8^l|p_ zIvf=T6;M=yyHY`GJ1M5R6Fg(7BtBUcm8o2LL%1?5ekOHkfE zpu1l=&Ns#R@H{D+uldS?L-x*gxf9r*Fcyn}XmEJg6}$$!Oh9o~UPY}uI2JysKF&R- zkgBor$k~2*oBzSU9!o|(4Q+>n)t^T-le8RMdnxkob0D#d#y?J?ewQbb~ z`#xPxLBW>=Omx)J^l#s+ph2%&@-DBh;1`#dwbCajIPYrCPVpvy^J6G=qBeiP1FaF|-ViD@oytEQc@d~&nUM^XmfWWEKpwHRWulB& zh@tR?&zG#gVzmcUsuGiA>tpCO4yDHEJxDqgb^-#D9toWWHOL$lp`%P2lf8e$C4Xd0 zU3JdoxCe5SxcIM+A(O?FzzI&X*_r^N+2|pbKD@ij{rFu_VjYMhP_NZ-wHrIy?HB}= z+?7IYAJ7nNjbGJrnbwS(RG+ofu2Oeu_LUFgYz@Dfd6y5b{36PhT~}9iveNsVt!E;V z;G6?TTC69at5`I@h`u(qtmVzVqh+T%!OWM2sJh&!Q+;ez18Dvo10;{?`PQXzjdzdk zK*CXJrhE;xidoEde=e0|Th6|BWIk_XY?`#2ZZ;ycszl5bV{jTmK@xk3$516o`az1 zd$REnQ2iAajUhwn_41*FQp&{|ASJ_S+8WP~0o$Y+n}@)Xgl`G!lFa4VGa7{nwOT0eK^>jyz=h1{~{~7IYehSdsq}h*%BKRXTQR{ zG@4D9=0YAPK&BzGU`*C>KM4x8E&VhqXSNTj3)~zWd^Mhft#iLR`nQ2!Q=`Lf4;Zb^ zP(mPo7A=K++$O(+K{C=mBq%G42|-rivT~|4AFsRPRX8w&`2JA8AaSQG#^yw69+?us ztJDNrx3b9Cmz{g-TKLX8>v0i!zud~R7hq4siRa;06`7Zf|Kb1{s-7(WTt zMvr@+z9kdf-(CjU@6!ib$1J-l7E?WnA=z18@-xoZ{Ii zvT;C(kS6+89c}FKi|6vCqyl{CqFd2=kHuI~T9bs))Q93Cq{YQtinHa5#RIYH&#mry z0ZCQ@P8%vp%5~6CYewR23=UY~k9kNcvx%_~<35s$!3D8$PCSZ-$tew-)x+V$W_b(p zvRRc0{eJ6x@|2qNsdxLU`ejJRF9)yW!YvUX(K|Zfs8kqvWert7zZ4G09L?eGNz#GS z@=luZ3r0qMZP<@~H34rQ_JjvUsR8%_G*}Ju=#rwhOzHx96yyb^ zzp;@KnPThd%G%l={eMY%b7tn_t^oMrugQZz7W0`6C50%!A_Jc22(M*jW%c#R`O&p? z1nD0kTRH6Ep_B-{%{*Nq^|w%$Mw>lYXuNWhWNvqe36a8Enmq)aXqnF3?S(=_*}U6Q zeANwCOjqCAGvU!H*;Z@RFq0wj$p9k>3UtWW_%{EkY=jI+F9ybnZS7n`gZhnTK1m?{ z?}us(DxZN{vo~>M5d})3}zcYL8!&`#-yK)4QepI;~Nf;oC!eBgx>rFIp8!Ib-u9JFK zM^naA9MYD*94kyGb^a`int5e*6C39Kc2?s=H$3lsf&KO*1-S-jH)hCCL0%Lez%vca z%?Y@dHFBTEha}_tT(of1o7`N?;Ihr%+&|2wIyq zHR@uxMc=;3C@BGhYC!eKFrgD2(r~}zzy?+;0kg_-a^OtUnZIOJjE*MD#l=e?qtNW@ z?{_|fZ4Q$jlEr=Ad2-Z(DLiJfbc_}vzWg95(F!+0gxmS(2dKx^)^ehTFM)ER_w4ig z0Rn-AQqt0kGj;Ap8*6=i&j5ifFtoV&LVzV}g_Oi$1Z`a0CbdFivSjp!K|3Y10%O+o zc7L%jF>wG@V;~lK_jPjV!5xR;VfqFKrd4FtR_ylSWan= zd97QOA>UiWX3X<3mP9k1Lgc|Er=b=ve|Bl3N(KH0M7fBJDQ-G~jA98<-pdI0lb<;g8`C4Cc?w$E ztMwwPunz)3Z;xOq+NYt3uG4l80F7I?ofeT+u~K1_ zfI!s&cJili|1`N=TBU-^^_2Q1`NlJ%fXeFqu#}WeOx5KVCQj>88WOW{mr|$iq&$cf zb)jAL;y~%kbPCvF6NaQ5xE|E8f=-&c`h&0xF2l1q&=c!U-a|!QayTOpWx}4u(?L}B z${BB6C&!QJc*4cG#6lJdGD^(n&wsA&wk%mG6>H9xKxC=J^3>2VkTH5dt??Akn}CaJ z?6buGnh#{%k2B)F>bQo2J)RK=f7#k1L4bS(sn2jvuE?NYd&c#5+^W5tmY1AIZcj1mi`9WyUUCXKHCYgx`EX;me#{>w(aCIua8^y zOo;zyrzxE1k%OxI^xt1I8n@aR4>+mD}quA1P z1O#mR-4XjbcppXgxuNg8@1J6lfTRvIPm$eXfrgfm=#>lL_UW&TouWG} zQ(@&!cMMMAIIJ$!triw4nci`MR;*->gSlOVtjg=s7mJ!M0v$|rzU;%vWpln7Q1$R6 zIay9)H|L-U?kS8ssishjN9@_&#skpdT3NB{pptsk0MM3+=k_If21YDy`dY#^dkAL5 zb$g2&w1-X&?3N3C3C!!K36r4Q&SHG?<*g7JIvV&7SwEH!y}c*%a&v{>3Jq;mgwdxS zii9{x0#IRYbNMymvs%FUH@%cl9G`$fp{_c*%;jZkeLw2^Q7%_m4w+s_N`39s{z!I3 zAWy}@8JAT^@OM;EOa$WIzkeL`7*KeCHG;XNc|Skr(763+PxLEST@YZ+RGC*BEx5V6 z^Fjcf^7Bk3E%hcBq^9O1VQ;-tO9@Z_>VeW;gI%0(wj@5kCdbVox@QW9bD5vwC}^xq zGaJxQbw#c-gSX5p9?Tuh&d<+7z@p!o$fOlZ{8&_UI zMA1ppt(QAa0mrKNeC9NxM(_{}#e|X)(t7{s!~{idOwQt`(&-&McFQDAM>ZxN*u?HE zFmlJukb#Kh!}qf9-)jW@=Y&I4Ds|e2bvwmoDlL}%o}QhRx;murc(`4% zz-g6>0}9VUYu8k{@aAgc6(XX)dH8JiHu)BN6VO%Ubv*|#akq`GrDQ27h!s7x@sAR1 z1)9^{*=lqi3IZS0(={HoCn7{c^=gh2}Z}t$tx);8NX_ zu0m{6tS){MzrBC|rhXI#1AON@A7C}s3w(UANaZ5UByPiytgHtRiKJfywYr@~?CcQH z8Yk<4g7>(%S_6X`HDOve`|{aXj4I!o%f~x=O(*8~Vb`!96l|_*61swdkf|Drq$v*h z>YUq?sxpZ=+Y?iQ?tt$KgI-JNSCl`o4@7}FqV#1c^=F~|ANpDI^ z1H~*BVpuy96Y-ahywBjFh|C-(al{k&%M%fl z;93z`Skj0i>+gq3w{q5bJ=9?n{T4Ucg7_-*lP zp}O(6>}FDu)}ik*9Dqy{S69ac$;iyUk65^L=%iLb$VaM_BJ z4#)5AP0cMT0~{Wy6qHk1IuS83Kl{Fhp>V6IL5v~{r)6`;m?@4tFqh(oSD%IyZUX)rw*26}rVBcpXE zCxGg(2XMvOUsO~j@t#IPTXgExV~Q!(98XtsB(k(D^~K|bb0%IAj9WE{aW_3o%_3BMg(RKaea;FPttDSi)mS@CS7_in1*3r;* zc6YZ-Ob3&>4Ye&O*rF<2nRq-8b^7a03DGHQg7K#V`k(4)g1M9Oy-o#bv!_K`o$4G`BSU0=~={00YBBhtvtRfA2cJ? z)F#JD7CoITEhs$1b8fCR&UWV2WMn`Or-s|1__H|w4fGAdgPrO6>XMR|{LLR6cQoHX zU!{wPm@S4!#P=AKgR`xi$*9t*0-TGj=bs{81eS9soHr zh`hIFv(1NZyd6zl2nHt20M+qDN7%eyxl1V0ZruC6HIu{kkPw6_Nd-bpgA&T?4j?{PG+ z`~_clm*{zsR#4>kUf~5qp{d8I+A81qy+@!#9I;Hg&P2?!V zOWRVrq$@JT4M}Js#mSTX?|gK}W7KLqvvQOclYGVWGAafe&MQ^DL>k+N-@JJP`MI;R z!{&Am4g(HD{IBBVud_!tD#QSr*5x$bb0JqG6!eKtiHQO2hoYkcy(7@s;yG#r2XCFj zc?#?2-dwzwAHENHVkZS2k*%*U*E3%Z1}Csv*Q$$ID4DBhI*qGYNuUMgXfy}o+Miq; zj+;(0J^%9b%%K;Hk+|5g1qM~CHlIbu%Lh_s>&%r4HVvXASCX*>1v}qC_wH=}8N>(i z>EkDhTz&RAKy6{E#s^a3G)z*e$RfnTQ03r&86ACARmDSCoK`lHm7Ai4U8;)+F#~$2 zXjl{+&c7}Wm5}`utOJe!gmmU>Nue&K1ZWk47%8SXL(1FjHq3bXwzWSH;-#TU%~2!; zc-Hsj5nAG4HMXpMcYWFyAYokkmC%K8NZ3~643C5TsQzr!f|M$duq$Azz%CAn^rT9K zX_mD&y@)uH#;0^}vkDV1^%Lm`KhG|Ru;Z^m6?jnd_OI&ObX-FQzm^hk>7YFy$^P5j1t6j*W&=e7T(X?V0RFS3bS*o>d! z4z(Xxwu(TMVAo{6mcJl&aQK**8uI36loA0Q#7kZyk^ooPW*OBNu}6yF1cApG*;m=( zxKGzdPa*m~>jY+W*2RNLew+uAKaM_>{8X8>b@G^VK03m{t=yT2+zDvgnIqv$XL}%? zRZ+%H+Cv0M=8&>;!e^{ezt9hwr15D_Ci2nEV=l`nV>)Bkg?#(vOk+Enx-}LG`DSEj zP|_v7AQYq3r0q%aCsqUS7T;KY$Gt%LY&Ji$p8i2*CYCu=)Q{2$A`h>vaBC&{1{fcB z%{6APNS+3J!xq1&6)ZE;aKm1}=c^LdJUAaFb@G23GD-~Ef-Ni0_|iuW%h7V!94=oz z-_}ilyf9pU6_)y*=+PZqR`xXv+s;=$5FnP|FmB1;Wmk-082~QN8It|A^X=55pm#_v)o? zvO3p2usDuYgjM67QT|<9#A9Cq&LnSG_qDR*a+^a5udg3pUmOt757SbNsxc5ZW1RIj=xDCa(j zd(SEFq@>v8SgN*OBd9ZICc-A6d?~t^Nx{XzOucF)h;g1Cb``%lkh#+CgkQ7*jr7mE zRyU|{iI-ExgM91hJ(3=Ye!d#2H5!HU*uW1r{nGeL#0JjYf7@-C=%QWw8Kvgao4^DB z`y#s?|H489h=WCVK@mlETHP7~Iz+Exyu0{Uap_%%qGm`Z^k{6jE)A{j63(ueL(6a< zk~uJu!Z>#ylE>sTusj|eRnEk>c~KMI>G_L8){3rS3Bj7#sV_&jAe7UfUU>*(wH+&l z$Fo$`zAVqN!%XDE!qR7=N#RMoRciav@gz>Arov>h)ZclfXdP<^enqDV+d`*H*79Hz4&zGQ$v?$g5bDwr* zFU^sHWU|h4vZ3bKUO(ENeAU@{U9H}3H6@05;=07l)ikE4hj%AVwiBaphK)gyWExcj zjNyb0(NVPZ!{f@$Nb4n{ysepLm(!J!#}bry*?o zWnDw|x!vWMQs0s@tdjVvz@4|W=#fus$p*CnCiazXNAUBH2?{U3Eo{IUC^VUvSXh`@ z&G&>dURr$z&SJU?4bHmyik|)q;3vR5AF37vAuJk}evI>`;N@3ge5L9y>zksBM10qaG+t2udqkwlp|r?ati! zzCPH_ot_T(S7fBLleLj;EQ`4Sv@IS}>~ym73f>xay3OTqK{4p{mVS{43G2je>y^QY zB`hZ0gQ(o9#Vvbp{?53rhzKSKjLpJL zYn=~3I?9YKfjg$v*<7v9)tNjM1|)W6l~w2BptSj#qU+5H#(l4rHa|&x_M3^MS?GP} zS1hGtcx-xMVSlsUX*??+%y=|4Etck^2++qTruDD~%^hGffT20*HCf3*wc zZqaOY=*jEyrhU2k0TKnfxxwuof%esD^@xRNQG?r|%3|JSa&I2;ebpwWNCikPRl3hs z4fPZI`nKo$OddHkoVJ)^52OG;^5obsFVi2*bcCTX$;lSqiW$Y0{K}CaY`%V9zeaZo z?ayFVl2Woe4lby?f%=CMx=1GTc#^Vp1$ zfU1H)h^y3@t3g@eY6B!Sz|S<*A^e$|0&-ynqmLfn|75}Yb$pa9_@U9|moNpYsI>tI zc>a#RURg%~P&DLZf0CVx0t*5r$Q96Ef6~$d$|?`MfO_?p6c*~`dG4FSZyVUkiW-V) zYWYQ341X>g2+h&vQ!!)UKz^=_ErU*pH!vA>2Fann=UmGI-{xBI*=`}g9O_z{W*6PS zal5ktr!RPos4t0kw$oA-=rkp;aRItOfx4Xc`T3uU#fgTPHTx^YIl-x+bCJy9c<%dP zt}Z@`7KWaCfkqu71nPUgP?h=+`zPeJq-1bvPFG*IaTSJaZ`+a;8TEvf!yQO|7KPsE z*F`{;VzJw}L9pgwIESz1!r_apcdoAh=_{dk6`%|z+cQ(FR32sLSm0?us2CX5npf_K zHqqk#Iks}dL`#pvL5k|CtcLa2mx6M1P7JIo6HuyM6X^POSVn;STOZF0P#Gi13y*-z10|R)3PF$=&X3tBE96K%sEO!2X zWu6?T&mexiuIuaX1HS~S{XiN6e);{Ylg&guF>Ea}Ca>JC@CdS_83bM0NK0#c&p04PHF8gih(?Fm>@vIpF1h z(a61~-UO?HTvo6n41_hbbtjgeg4TP=q^A~cl3qO+V8%D z`vQ{R3L55S9k>O+SMw*U zET-qiI7vQve6R_k=})?QyU`rC(Ifp{svc6H#!;Si*p|1SaL&sF{Vk;}}b-&~ykyy^AGh9qHl=d!hm z8G}Cp7-%#w!or!|-EuiKqQT&!1H+DARsNenw*|Mqqm_OTfMtsZ$_e92O6UwI@N9!1 zWWkRDsVv&N|L20htBmm<=f^fGW`Pp{cf3IHfyfro;vW`ZN>c60amdr_c;0i5#queT z^TubH$|OgOQWxJCBnp|$Sqh9{bRWb`F!f_mZj52148h?x*{K;`Y~Ctf;j$Y##S`mK zdOLb`V@vp-8ysgbh~uEAuLKa~uJ(rv{-n%C$%c@>o$K+V={V%ZFTmm&p`aan!^ ze{BPhoIsN~noaOiJodn>!8xm-jI1UxW9V$wE$i&}fNxBafCdRDwE}Cc#H1^YnpmsD z5nr)ALLu@L4k--$_0A-8d=8!v?Dsc-Ch*ENtvqfT9J>J3vO79kTT^2+wt;%3vka;= zSQsmo3uvGLdn*tlZMp8_|CCIl{yxMa-7nMz@A_ca2h`-;-F?VE$Hd3QA^E!lCJ>;e z0EQ6I-2Qa9M=BC>7x+GnbnPxV?l0W;k_t(}Vn7DHo^Uc;2#+(&>3mOvlT!kye7r@% z-rn3q0OTSU;P5gV#SR2`0C5H=?8#i9{db(@v|gG_YkzfRWq!;^+S_9yo`z=-KxAkY zXd@ut#>U2eYq97682azu*A{8i55)4R0VS;qv@|arMzSrg2y3#HJ=L?gd|g10N*BDDmfp2;@4U!5Ms zt~f6n$N??I%1X)aY3*EG<^f08-1meU8z}+7+kzk75HpEk$w-uTgPN@IQT%Hp%UO!6 zBOnCTu^Tjls{uTk^+7l(6GDYEBN+BE{Jjw?-%wB+>oh%7~jGK zswXC$MF7_b1U@A$3IXYQFqV(j!7>qONQdNzE&&zyBkcQq`y-jAuvA?nY`O)^#$zC1 z3n}qvn;97?t3fy{bvI6dCR!m<_!#g#hVDN=sX>v&sZ&v~qGxjtlsBYO`&J zomqpimCn5#QBl=8m*L;Kg;JG@)RmMwr*SG>r$;dNnMSsKXBs%ox+C;~trUCFqP5*l z-+zTqE_ha7py0?vAc@t50HCc|5O$rZAGs-88&*GnI*|BnekHqT_Ot=$6D?=Gs#PdAnnKw&)*=AFUA&HXF~yf(Odb_x4}K^(hx?4kXln|B+ExTl=Pai((kqg{rEg_7~hoe3jBg z(+(vQIdV0)fhkHl^cc%PFT>El0LZrIWMr_6I|A^cOX!{yz&!$tyKezeU;-V}7{Gi0 zgrDY1&(zY=QeWTIng{3KCvK^KU%s4tC+|HyeFDq_Z;`0L0{!HK<;|Vn+rd6pnsB(` zYzhBjvu8|rIG^W&fQAMydSn%;aL{yGR0cfTN!osM5W=}RMt!@bkNS2j5kC^)Xh?P-;Ifjvmb9@Up$pTsX{N3_G@z`7X`Wnn<9gwi^ zH8>x~^E2oe(zT$7e&T8R;1FoO(}z?3IVi{gG?{a{4o*(iuqonr0Br#9$Ew&I0Ctbt zac?Bw&=Sz4Wi6V=Cnf+WM5vKiEJb`)7-G~hm|oJ+@BHrwcqk@XCrQbmF8ZN-8f?~& z1`?$?Cas>`<7>%6KYc2+Aag4=)WmuOKsdHHv2W6cMS^kX3r)`>A_ifOpM#L@yw+NL zt@l=8CZ^ruHyV!;rUWqd32bmULsi18(aoLD?LlAW&bCf_frc|-C@8uUi&eM)KYMD7 zce1iTy2HZZESqmY%DWnzK;i15BYNSfV4>tHZ>(11pMK$;Lo zKlR#&>f033$BOL$TypIh?;8MKW5F$4h}jSeJ8AyFa_7Uri%EdO2X@n7$LJmtmMHX4Iq$otXpqg3vXNj$eGI4pkktJ@ylEi|79tR#NE5ipz z*U8{AlY%#aD9IWt!O<}>DK9;K0j2_&%Nr+*{PyH=Jij4yy$szjB*6NXwcOa+ipU-g zU^^mZpInz!S^l3}C)p~~Vf!bqw;zEv<*(+xE>W#Iv%@+5wcg%dcDkJd4nD(4t=Fkl zhj>Zl+l+6wt7sbW14#ZmW)4VM-PD7#4-yvHhS6j#jRsDJy}4R?C!o^r!zHD04wA-u z4>-8bpFC7aOB=a72p428q7=jY%E-n|-a{&;Bdw__jQRh@yi+)S0 zzm0mv1jI@=e*h#t;BJEnmj?r@R2E3aQ3oCj4)jd!<4jP%5T@Dm7O9I2N+$}n5~A#Y zh?&d6(Ox;-^L4Q}S>rtK$g>T?(dpqKM)ieIzX%GqZ?T zr&sLk>wwoF68a;O<-H!&|K|LEXe$8QDnK`smzAl|ea-aE1p8S>m_6t3FXDs=wOBP! z*g$%Wejmd%b*hLIS0tSG^;il8g*nJR+ghuDESozl*7D^b%ZG<4JEOBuw^q(H@w|m56X4XG5 z4$xk8c6&4+#OQg!$ytlT4D$wp+-$GHUm60AZ=v=qAvg;>TK8MFC!Cz6fbrbxOgRuR zoeuIlx@WuW@}rnqE)w(ehteM6N&sn|OXAE9P>`z2_g{~Iu+!Ar`=dbI+J3!AY+$W&(nr5YkEAtbI|Is9O&JeWCU zHLJ>wxhh-zh>HtP;UrlO*!BF_;^SAMLU*$^1D?(mDA?pe;UpMh+d@+R_hWLVd`OC8 z9sc(*8KaZNZT&yu1S`iqyf&-v0!aeAb9WCxE!TW}52Q<}wr*uQ1se$Z3)ZV~)n*$9iYh)7?ZYTE%ugM$bnM5 zKaezAe=~=0o1k4uC%gjk!4OdiP@^BriKHBGyNp`pGj@ZerUTu1h}E_5h~JZzn$3Iv z?v_NdVe{T!yqNeP#$!pTEMrpc1 z&jAPq7M5Hta35$~>&E=gMQ4GF{w9;lgnXYSNvbOKp07aWs6NIj^;p)z(QSF-QVig` zU%q6SYu^7^na>UexEl!^U%(3rh+gkU;{Cm@R3eR0{?-tI)SpUd;9_LZ{||^bE;-p_ zH$HZk6o}{*7S8(nStPTXN&(vyH(1Ml1jT>eAlk0?2XKIE)3N2m53GuQz4#Pdi>$Ft znUJI2vS_8`#;8I%z+`=$kjF%k%G*11JF@7%5gO-Qy9wOFF2jJEKo(rV}KC@9tZ^8Rf z0MiyNa`;0zccBE-OHQK_arAYl56U$0zcdczGC5<)&bxQXt)3@-7`-#_Y3OnS_h(@= zPO3%E4A2Z+Az~4izHuYHV4eKR?*z4O(_}06ns_(7w^dXux|?tn_ke->YV`6+3y88R z%gX`R2b3RSe86T};v)7`_Cf+k^w_2}EoM9XC)T`X$(YtGC!ep5-D)n%?eQG-r7=E^ zitl=3926cwV+#_y?_fXdfsuz@uz}0(%PVqm7&_?#5V($-X z&3t4mR}bWn+<~sNo}Q`2FLxkP_L6|P9AWy*X-Bn^SnBdK4>gk=q5pAOXz@~yaY_|I z9xaXHmY#Wo!2=CRs5o?}CIT;%ibuv_>l@KeFcmv50mFs{~E?y z3rQc}B)ao@ctpjr$v-Xnk!<7RHDRG^3QmhB+^*X#gJPjhigbRBwSe&q02XNy8U2YF znrY#MwAApDt-&Bf#?7t5)Ac$}70mG^2f8r6Cp(ikEx&%Hb?wgXEjClq(oR;IPU4Ij z9xFrqL#^zsHvzShmz%pDq)K8gk08mX2WSZ|QKXSu{m>o982PN;m@0uPE+*uXgWG%o z7CbBs@{@tiRXKpt2 zfb3>~yS|}pA#Ua7#>S7!JnMR(gnhltbh8H;()Bnnvunt{LHKUcS3KLGycgNqaBc|C z=1LLQxBMHoXF&xLnsc>v-e~DBJRuhtSe=E1qo)KcjQLdJC|Bevep z`(rc{o|5HIbQU$W#-#ep>&$t(-5?x}HiO#cpGdAG(RxYsN%oh1^G&OEr*b;o<=VCd z*jFa@4jDqfzN8H3CpGV0Y#aEwY+g!+WYfQZYt@Fon?vg#Uc-BGys}{e{yo}$D_&#l z@r;B*e_~VQrZAgFyM?40tq<|u2+aFpoZqeCykPXY?L;t}gJV}D*!f@m^BPU3lCFZ+Ld*sxj)NgKs@ z5Q2hM>AA@72w$i)Wh7qtaN;q|WGy8%aPdeJ0G04qkG28mtw^Pmg@k8hp{f7-n`>`x zZ{9iHY{`UzZtOnfL=8DPka~Xw*1R{)H*yXRXF0|G^vI7lXU2B{p-)>oA?+EV@kF1T zHqqDFgc{4m;;(d?MO#8?y|&_0Sr-@Q32c7QOJ`S?lf%;_iHGl>^JvjzdSf%7eHO=N zd{kOpu2u3vrAmUwXy_*>hJlt3-Fq~kcUI8W(wo7`zzhH#EXlRy#OsIsR26gr(eXBxJef z%ZwGXY)7HqS%{F3-7!4E68Ex(gU`2Cv(7_a^GwSV&wW(6f6S|}g&B8`TJ3R0ztDK! z=5VmtUQ*`+9~T?@dpt;y2IUPVPJ`PWJl=!JyuqJ8KY667V{EK%uHWc1JOYI7Iy;)e z-MNf^t?)HAM=|bF&k~&=WLxRHl*vt3n21FT831PG6_u5gSCXtRU)pU?%=Z^w0ky+b zkmFDQbf4S89F&cX?d;54IP?&JD=`xKJ&53Z+}y4Q--uIFw}D4AjtO-=`(Z=v8==!c zp_ug(F90Ea{+uIt5ZlRBItnwH)pj6gl@>_iOG@^;oZ5s2e~#`vJWkRv*PkEAZ7c_Z zS{mvuXB+xDI!rnjIiQlgvLfy3^@WfZm}3^{_bMH)-v^F@Je-{GyZ8ROH+ZQk&Nl{D za&bp-llIdF;9yX}!wXv?iSpxcJ@ORQUWC*CKq0|evwCvMm&87VX}Ag=3&Ih(aYz;y zams??+ewDmBw}O!BzTF7w3tAAl{ok3%|(+6vksJo3k{o*ko5b~x|z=RBayAi#w`73+++X}#L*x?duQqaKWEF=*5ksVaW{jL%TD^!P6z5qee8D0nF0R|4 zoSv>{y`b2##N719AU{z8C#_f8QpodHW%;J;eyTKkk_ETR1=Q+vcb*qZCa{0U)){^+oG+^xG z76W=YD@#w3EHkT-tr5M5c+0Y7=H`1ITU~(wGWL}G>}zTSL0)SmhQYo76q11+Mdo3{ z5G}2ZnReC@Q!Ea@dm-T~*pH+&Je+ZF0#%gq_rPV6VLl0yagu!44i0NDoFrCF`uId_ffG#N@ z3LMD%{S86f1?)+rMV@KZyS#<|=!xJJF*7n!F#abaE`yj?FQFdZ;bk;iuT;P!ek%JkMVm39RLIc4qbZy;(TS23VMwpeQct(3Df-1d0k~Y@O}xzCbBafQ5ziAu%?irj?zU z+TrGMkV2BEowLQGS)ZvnM8y_!>h)+6^8h5@>!=BO8F`?9L1r*n90X_`NDdGB4-(^7d01i}ceh37 zW{!u1W$gMR-MAN8n6FvcxnuV5+kM@?cexo0O>(l^HDNoi&*(=)pLkoU@@ao4t-UU; z?^KoH#JSqZpNv*IJvv&kB(x&uqCNI!NOjuAR^~Q)El9Y)@Zq!Bam+K7LZf8GWnKXGtzFg$*L8v(>u<|88`yEjnQ z+F6r-!skFa?PpK-e_H4kr!pRx@p2Gw9)T3t)%P1g{HK&0PKQysP4jhi_M5-T>^6_O zx(Gm9tfaE?Ry0h@^O`#PUGzRMw2KT2`}U;|SwtiolU6C_ItM&ElfJ^;E$+W&eMSN* zQ6r2f=5(sXiFv_XJMhC zLoc5)WBo{x(A(alaIIT9rCx-q<@3}_s^A|Mpk3*u_w!s~FY9~J!5=>sxw5{p0-yBz z>q+qA1S#<9_5AlNbnvIm)U8+Yx2M3Mid%pxC<5bi2AWu7GY1mHgz$6wS_$VC4-d^> z|5(USMo!6dAz20EH{l9&9k{Q)uLq`O=!h9>zw+q*^Y-Aa#ALPOROIMd?}C30O(Sbh zyOjESu0tSOA&+~1$OUGc9fi4W>?k5uvgk=RB2v*TXCGVMooGDMfc6+dJN+3~Q(&|1 za!`w*iS3ez7eTGM~D??}AcUH5S;u0#41PpH7 zYhB01&b_8mX7-*)ORv~V^k(*dpEVSvu>DrCR(Wr`&XMBNil*xeZlkiGMe=bIXuO=( zY0t{M2W-ZYT<~Ms)ID=-O5RY0GNSwDYH=>7()>0`XX){&$va+?UNM1-jI){B!_%8? zdb6XbZk=EEV}xm}QS&E8sb=dy4aEyfvW>t6}DoESq}u;D_TCrJk7CaS-x+R`$R?M<2U!^Bhxb{`Q( zq(x-V4|7iLl~bjSHm2)`v5R~Ydx*O3_C>3b628D(g|+CmMdhp_CMza4m73TKmr0ZJ zxie3X?NmNS(|r~_Xp`@|KqPRE*-l>KY(h}9pZ-};j7)m&x*$eUa?V4Au5WyA$Ecjf z%k*1Y7^Q}rvt`#+#z^PK%^qbQQq1V43pUZ&>C&H2wQX__9RI=9dFMGUCa|YQ6nP#I zHA^jc)NGsr0!?#c;rLymU@R=;)3rs<@P=a**`3>O2TPXME<>NvZ)}ZMm`Y7rN>I_P z_gs$=N!!@nTf38v^LTCE!;DM2q(bF{frAG=jO5eOcw}+itK)guiCi?CtuI;B0)Bf( zB5T~li^tc=V3HYsNen$|VofFoHZ|rd>T-g!c0~7-d%vSC;~+&S zSKxGQO{pcaf@^AIRr;Ybn6M7s(buv|FumAkm{;f!T)JF^I-^Y{1Oy*~*=q4emAdOi zn5p^IW>Z}{z)k3UZ}B+p=X{-F@1@l3QUhPOv{;!ZmNB3Cu%fupM?LGVLwlXEC|>#8 z-94vYkHbL~ApHL-`|7Z$y1i}G#{fZ*lomzhBgh8dXId#}CL`o$e}!DBn4v!=7o?3mucnXA6WRy@RWVh_Yo zgJNR?=`UYnJmPhj6PI{ig>pdYH|cB3;Ee1P#ohW zHl6jsn{iCA4RVUnqedVEGV(-AFcbR^7I>=yk9CI4um9>FRkm8SH1Ray)N0VXH&CXw z!+6%3Dn_BUYH6I(tXpHnt<@B*dSE_IxY^OHoPr%Lc|rH(w*2f&d@O+xYqZPKL5gF|ql3%Jff-w_>W>Vb zt+i4`)*dW__wVspyju1}N*j&FT8X{d2PTMi-Z+H3ZmKdeVd#-DG3+Z1yU)p+_7h1b=Je2sXe8T>?i&@d(!?~X3j+1Zo+%CCHysOhre>P z?^GZfwM-^&k(F6d@0~uMew#Fv*mZ;E0N*{g@8!Kf|h% zIFueM7Mc&L@d;O1j1wSYNkW(3qkB`+g&RYud{@nLcHr{3;&`FEuY)?^v-$Ob)~ly0 zq(r`l3fXWZBsAssMgR+3Z5|SjuJ`usv!91i3i1Ym6%(*Y2+FUCfs|IC^d_1&oPYcq zD`#wqfmxmcM*bF9UFPwxPbGI2iev`G11b->)GuXLLyne}#12vP?%nP2G%(wX7MCil z6sluIwHc8@6NDAghkqM=xqg@xq;r2s(t}Xi0V2?(9bBSS%_}tI!Gq8}PL1?ovxsxV z8E%2Sn+PU#-fvjSA`*R=+9`2}lBH)e;S~3xDDL|a0a4QTbrZQ<+fo6hBo@fe@xZ#W z&x%qHo@ArA;B-b_KA%V|ZRFwU8RXn%2%T89#Dt9V+-48~5e!ZjMI!tWNTnQuu(MO~ z;^ey{x`G6=#&@)O=FJq81xn z$hoin37wz;Gz^$d@2$xVPZ@nWIG`*n;0f40>iKz<H(I!nDjV3C0mB&W%WCNPwXkkD72oZcQY zwjh^MCFp<+vn26JRG*eWA}=A32V=4|)EuO%r7bT4J}Pr7l)gm6boOa;hdh+kG{xpgVi8#8UUYF1q$gR~lar6G4*4oa_< zl8r|J4+&D5ZbfBPRb|yo1-tnqmM26^cFHv|FggCU^3-PM)v;(wa{Lbn%uA(b^2{vc=zNHH<0)(R@surlG;kHXt____m;E`;c|; zz7fdD#NNCqcn=stD?p1usW)z)9sbeI2=upvBqeF>VG^T=>(0M^&&0eO8Ubmk2QS>< zp>sLDTQ8fLneRMAL*sVSn7PBfca+8aBFHfC9jU;YDEgjzj zL)_-xouBEZ4T=3%3?_$PeH`TIaOQbGzbm(-v}=le=srt7OWNHt{Lau2dy9;o#)Fx% zcCnbc{-)51$1^h-=kBg4qQ0$eoC4&sYgI02U6Dz1Fz?ZlG!oX^j5yl#@VSU&)^Qt} zJeTMn+0Jigsi7GPOQw+D5L$bDz)Hg(OeY%Gq7*Iz_M-reM+bc&iT#90o(MdO2o^e= zn~Qn>k+Az=|600Gx%l0e-f6kHE!JPX0#`lx9Y60x{SMPV459|nRN~UJ{^;^jm(C95 zw?8SEJlDchDZHY*Ry~_^@}0<Qm%R;2TG zvwn{u_$gpl01^dN;dRllBrdx9DtM&{CYyU{=K~qtZGO3b|Cw|ENC!Rz1!n7s7aQF{ zLc0ZM1f~UJPB=)?_#7$Vufp4Yr3nrnPWn}_ZAM(2JJEk0cHLws8@&H;+|1+s*Q7-6 zCNJpFv#T?8L&MSVwfegth4gMq(bbnIsH3$shj6q<2GQQmB8w1(Frrf13Y*w4{yY@2W7!e*VYT zp#9UL16*_bnr6%o1)0@K56RQd~)m zQo!}(^UG&kt|#^(#>3PAw!bru7ZD#XoAPXR07`Uxyl1*BH!ruaaN0fix{tyKt1I{0 zqo*iw3S=Q(lY8g=w2*Im^JHkKsGGdF>Q!u>1%%c2b{Ju>pJMD)7v&t2oBX;9puFFM zuy@*&^Zxqh0tyJyw6vCd{(Q8M-QtDwmw$;`OhST*fq^1{3A+KP#Zt*!%FU@!hr2E} zm$({3#CEFgSKL`JkTQsHDbf1~M6xf=+{R96Wvrk!VVg@zK%Lya~z4d``1lx9(iL*(0uTcO3_vnS{mmj*fvu z_Dmgw0JyrqaN%J^l#t(}>1uxSqOqFIQGa3qjH~_{vZdY43&*28XejXupenZ%*x%iQ zQSWN+dAdVSYH;?66f8ac50XbqJxO!~p}^j`x1{Hr*kg4rnOH=#yDLx(q0<=ix@!l3 zt5FFF^E*GTzIwY>L|HBB(RRv>mjn{?xPp~PV$z0<|NKY@IC5``8r-*EP9PA8r+wV>3fPl?>$_C$*x zJal(!zF3Z1jB$LXWr--Y!tR%;E25k4QR=<-=-d<>GdR1Il@%6y-)X_qofDG1<(URg z75oiS_1L($&7B|orikW>Wm=Byfw`KNtrhTz2uFe9UhPT@oD=ctJBanAky zUEII=Q@BjL6lr3_$T6v;RngfvU%UWBg4zVu+{eT$FO{`*lL7Jms}K+~xM-4%ciBAY zZ`N^Xc7uCm{8d6tz7ookuSP?8BbT6lTN$htRxQ(c2ASf_Kz=Yp)6r?6R&?z$c(8b0F6Fy z#V?sR{DF?S>*+NfQ`s(<0WShk)VZv#y@19@0uW7Z_7EW8EK#7u7rNzp%WyF%eD7mM zUX9fv7{8dPsJ7{ksCC^hlA#q7w#yra0m3ZZW3I2hPy6^h-X~dD?Mq=Nvrbf`Q5wgl zprB})o=F9)hOLR=SKb#h_3oACBKGOjf%VSojBr4xG@D$ZC%9vL4@jtoU05DH7Tk)! zKH1x1wi`VHH+@G($Grj6_>EmEUY(PBa!+OF6?IbuQPJeM((LD+vc$}q*~i5-wXB^7 zS{_|pU9I$`90L(+fJ`~NxNwJ+Iyt;97&1{_)M2UU?&gCFCc%YP8+JR7Y@MPIYDD#3 zE?Yg-zNB=&*V-?}QdQDaEUc{8SEutC;S@gToHaYT8k}A2d?sTh?h}T_97$IK*G)}L zZ9iWLfr=|=r6uUUcX1ypP%?vjo8~?g4TJLt2xvcl0?OwlT9vbXeaYx7_TzgNQ>{;* zYc@K+0%GkIbBd5pKq=1Ba)wc>`JL6AA3DpQW*_$ENH=_FIcR&b3UDYlaEW+<{~hg* zg`T9v#qG_t@mTZ&r8$p=<}h-A%Ax|kclMr&_CYxG$fR>21T9R?ud>;dg4@2A1PY6r z`1CKR#eb4#MJDBA_3M8bkKd8()uw8&DE`}8Gpivr2| zXWyUu9BnLnY>rABW-w#PKK(vEo@HkoCmF}^-c_d292MG+QW;Z$Pp4zvF23R1^y$ms|V5##bxvGQn9tI06rWq?7XSB3d{8y zv81tc;BC|BB6kW}IbXvVayMyxGokVEdzT%DWd337W=7%<@c28;X6g~A&iMsoA~aB= zssoC)%k6!H!9ig$9|&HGI5J?H1u9CtzBmrPc5PZ@RrbxeaR!*r5mc#6%l~ z4w}E&2G{mBPbZZ^-8=(q@23zG=Bvin$@O)sNXawMO1`tao(o4K5s-J3{g198EoMkM zlGc}3-uHH=YR0Bq7nXbDjHICA(Q7aLrfDJvW#B9}?_UG{K&W|l0J`40chu+)A|s=) z9DT*epZg^991}np^Tt){W$lB)?4^PB^;6zXv*8sq&?v6eyz5kBeyl*<7fpZI?W(H# z(pSN^--xx3WW`UBP&THSxxkTIOXmgQRbQVJN0No^k6?!TN}_=!&+F9BBXv_Rq5YS- zvNl$F6yARVEH-J7_d*2;K0nJ64UHl3H~v(HVKU zGWsiw@%qD!BA^GN$snq!-i?3(!CrYR!*}oKVn7@v4)|2%<>GLA><(rQX23xVdVtuS zXL;nW!0a)Y+SFQYd46029UdBpvh))y?mRd7jLSB4)eBNGO4i3j2JnzpYVYD>9#h)17 zI@J&Zyu=&mbyHax&_AP<%ebS|BP}4fY~0sRe{d)W;N|OUYv>Rki0-Rb9|Ow8-#9vU zlvwYKB%rgv1V5B^>!7pM_(}(@^jpxNiNv954Fk~jW5VE#@%^^x)N4^mVVKhMyuo7yM8V)zXs6qzj~{R^`1HMRUhrKnK{$X zby)hM5S0mnH2JWffA0H`6qG?3eveD5_wN(^{d0E^_8Ut@nVELWJrphp84C-*A55pt zwyvz~!#_HK931ovnq1Jt!~;;>=(R6R9gUMHdvAXfVj04 zm|kSNpP?hVNOG5au{+ndCwRHJZzN8E;4oeSUPOaqdploAe2br-pTFoXiO2YGrmKtq zRA)!QH)1`pt#4$c$o=9GSbtfrB*b>~-NH!4AN=~&rte)Tg(NOE4vsQ@U+dH zw*s*S*jVSEOabn&VVIXP{_nl4fFsMaMm76p7sKKRu{`=`Ulsst74jslpM+-x7I7GnnOV+vyV-qhK=4pwiRO8FAFcNqRRa8p$LfFu z`1kvdUOTLPXXfXZRu75MTlwI9?-2tH4gPn}v@Owgm!msT6_e7MwAC!@IK9G(P$DlJ^ zO}`a3H>fPA2sYG4c%|pnGS0VLd#+4kWJQ&I1X^>&|{4?7VZ^Hn1yir0@GAc6CaSN4%&Dz?c z^a&;eBBU}oF#&9D=qT8a_afE+VF2bB!k#Bb_4vE+Pk}!%^#}YB#n=FS{kgtm_L=OU z&9i+02ickE7uURwKB6Q%&(I%$byg|`U-rw{IJ{!D@8esfM0S*Shet0o{~UMkVdf z0*X{kE!XvB#=8tSK@OMWR_v)y^E-u2xmc}13iImKSK^)2M-eI)+>-dg^yh4~Q59rIbVPIgy(rK}opQV9>yDx=zvmDx|SLdp&h0M zd)SvT`9T|8s1H0J3>P{BJB$jE*(Nj@hV__1ztt9YhEKO!js52qqNlXpq<1)^Tm>9AFzrv$UiXsi2rfkZ=;Q_0BubP z3|8J9DB!}2$bpiE;&ZuqG%F;e?>_sV?;fW>TtM~*R!jgJh-dl=JgG1tCs$=U*XvWY z<9IDlkat&QXDjWsiU1X4pYS3&-S*IJr`_xR#r%f)`g>9jfsI*{%_0!-lBV*w@b1uS z^0+cRh!*#MOjPyxGYvgG5nmPpuuoD_8c<1?4chl2PInLr3~5rzvDWS1(l0VIG(NxU z){I170NLExZX>Xkjf{<5SzE*U{5Cxyft%Fe+xL&|=WZ^bSqJFekCC-8aB&HUo41z> zae=8KCZ!b62?wQ%;ap~3GO~pQJy5{t48`L#AAbP()EWtF8j?7yJ_Wb{M$%A;maEG) z%ERI1A~y%ah77&OD&{E?>oE<06b7>0jLgUAkZJzwPyqDt!#}kh|IPBmo zqLg|G>r->V)>r14TieN*o}T_hG-Wy0Ed5}8Yue@d@);4)-9uUU)GOW10@ijz+a#|*Rykka;nIKFm; z0lc*3>Jn5+z`O_a+~j_vN<@t}uuoUHozws6oEhkdcp4l0MJh}a!|)(c1>m!cPn-Al zQh>9nWUQYo7A!l+3{2Tg&QfGjK#T>lKENY`!iUfGI=XG%ac$3+?kBj_d+uf_m~?l^ zTx(T0yDSaN=-I3X#`G?&f)H_WmD4bJ>R9_rIt zo8j};)>d0@C7b6lRc;+a3kwV1QW}89$H`fDOCK(c`C9<0Jy@3P3?(}49i(c38};?X z^hQQva9W@|e57q|VBogBQCzWEygb0lSy@CQbG@-zMDEL+u$mOy+F-I>538$l`Efr? z_{#YiHwCz(wGIyYoDzzpI{u7gA6n_bC)!4f^$NosYTr416e_nixH`2zyigTZeG_(n zW6LpI_~?CpLkF%4&QkTc3u>`vsLr*n#DXVD(A{VdWdI*EtqOZN6O;TcG7>8t(XMK6 z@97+Gki=l44I%>ZKhDhb_o`y3`7MXA%^ z=!<^v^V%IGlMHc^xZ!WIDL@KnW@a{4VOR)1^wMs0PEAgx;}!g!@qVth^w?jx3xdUN z4+Ss{XX+iC>^42t4?1X77zmy|eRH(W&cd=WUDq4keGN&M)$ML?_gQ639Zce#>+I|V znC3NR?TWKM6ZF9G$FX(Ys1hkZysGc;m3A;Urr_KQK$zQ3p5z5yjYA#T#$nn3B}?WP z29_D=H*e%U*ns6Lu*e2l>5Vw_>R=!An{0y7fN-}n@3P@o9`TBTcnfRiGT~vNTi;^~Tsb5=2 zs0d=L%(H-ErQ!FQ*o+O~)&$GOeve7SA57G- zHtKc%oiG+@I-s--EhooNC|2;mnS~z56=(3QGe_ZO_GZBmI&$&~ixm^lYL3_|*AH2( zo@C6yIe#+dj^yA=h%eg680UC3`@$+TtUnq1%e=K{@Y4xcRT4{x;X9*_xrow(rP7IH zH98LXTna52uTdp8iav`}Kw!_TG>B23Jeh*>dnU%kiONMcr8G4@J=9VzK|Q3`Od%cU z?vBNl&1a=0vhl5<7s`xYr*?6vM;_#pIzFrV!WT+YMFfWK<&K>OWyu+}&HlwwEqBsX z>I2$PnSi9G{Oy3U_uYj>j|omWbry1mtM2HI9T%D?9!z?M%$x?9Ww{lXFy|*4&qyNH z`376Raj^K@bl!KiFN+;rV+%AAFd!qr_6CNveu1YN=CVVNq0*?lBjGBj1S+1RG}A31 zGoUnVIafz?y!dJ&j9&e-IjlfQ!W2cz#m+A1@-hrSig|c=e9+Tx)K=f#WV|+Zo8=a+ z;|Z*uVB%YIC8zSw0Pr~x!9}Oi`rU?t2!1#q?JMpymIve)7i$SSXI-NDw{g3k9D$%= z*?0#;z;sV&=>UC~Ax2_&NZ#<`GmC$AFJRPac5f_oq4HlMhL|`?Jl^+3_hls-+)|K_ zq;>C8d#!s1XhS5$#@5>!Qj5sRKs)<%n7VRYg0!2aW03RQSPS{!W*o-+{QLq?X!zb& zz)p6gB%SvLf(Omb&8_h?(g77t3jYE0E%?c~ZED@j+@(0q20X&mZpH9a4n0wEo^ssW zcpX>9LPR0TIZx0#2I2lQOeM?~JaX*+2KUNy$~IBsnZZBDXC}=$(}K^vDV{iB&)(T| zXLW&fwLq!WYN)Td0;?Cge9CuP8Y59Glq0#_G7xPuK2*;?Zk`g1pH z*EyOQU_*chhqoHlT3;J??2VNM zb#0LU+AMt|^((U%r_o8d#PB0#D11GnpJPA?GcYwBZFcuBD^8kg@;u!*E(CJ)OmQmX zU;cYl0IYk!!yXwK8B8ie?`ZzgUn5%>bqJUWJr)bV@XZeuulm$~#_75l1f|!5f}V&n ztHHALwh8m<*7zPUBb}lYY5E5TseFL2HJ`IJ0_>TQEQ5@!kMFnb{Rk0@@mKYOD`YJE z-s+SpGMCmU-Spw=d2gm}T0TpPu8Fk^Q_j1W?yl=YB7!@-Zvh1#v>R4HRqqM$M0Igg zEAAEz{E=lnCogMOI@6T@fcC-S(c18VH{*3c=sIt|5$tojg};b6-o~hrGARIvO=3dG zMJU8IGVDrbjOB!LT}6AJS%tog0;qcU(-u>i>n3A!`eyiu+(Fq5Ag@e_I}VX9oPF<3 zy#~XcQT~guY&t{SAKGxb8uhg+QhOaa&D%-2qx0ti_3vK+GGlm zh4Ni%H2bHMBDgxUt_K1|M>df91MERDk!;3hv>``FI_K-r_W?Sc^L8#y#5JL)pn0CP zIe6Js8dXEW@c!x}daIR{S=ATOOy9dgP4H74@?Abca zAUed;mp{FZC4^Td>{`!gr^Ji0?w;rmBYS6eWH^1DhlgoDMcs*U+SD0E%nEg|pMNgu zWHZZ2 zNDCCC=;(_bcg{S3CD*XA6n0QYe{m9{T4P^FHm#P+e??UHozK-MgzfMj9@uVgZ6T8q z@p_P=zjFY+N6Cbw_;??mzYGnGxOjMqRm0y6<1@ zCg2Hb4gjhx8cH|>gB|Xt0`(Lfh*$>*AeCr^I4vicU~_}*?IXoW8g7@a^y|<=Y6y@0_E6VEt?($?ElEqH{#`lX#X19Z?PA z)q%Z{5k;`lrwjRo5%GqiFRcB{V=~GH(_^OnS8;?GuWk{nAt3!C0^hz_Dwr*we|Z&) zDy{!ip#DkzB)YMo`y@+z$-EMQP5qQIDFtrW z=jFBb`;q9bycc&LdKvb%d*4GNW;N2Zv>b?}AO$TU(8I2*F|%9QBJqDLityN=gASf< z%~XP7AJA$7`X*D={sF=npqowMgGxBUNKIX<@6U*pLnjHPvK=>iYDG&p1UE=UdJr*zGtafZy@x)crkE0Uf7ihG!^^Nd|E?=n#k!W;C^{%@5J+G7Gg=E9}BsQzHU%##ASKJd} znrqk8vIwq*}GPcQgmH9N} zV}$9y?kUpX7*z&OgIyv6Y}$+b5&O%ZxS6<#@KCWy$GpYjEBm-SHbFu2PeYr_MZ?8S zmj?i+a=6o}O21d~8Mz1Z+M{@H*%e$IL!%C|xZTeU4&kd%hn?8Aax{B5W~ZT^?_rdS z0Cj+4>i1K@nNy@Y(=A!^Gc9VymRUQ96k|(s0T-XNl;SON8I3R2$kc+GM!tK%g3D6fY6P~Fy>93^kBw8!3RF@+c%_&VIVle-VUZM7Rv(Td7A zuCzilZNDpSEG9%MCy%dN>5M~}F(L)|bOSqth?9?g6#(nCIa=y|ya&auV8>vc+njmp z+mdt!6`3i6=?~x(4Oi`~1Ua5A48WdP15jN5+3t_Zt=?0qrhB$%_O(2>6-sYSc+MK_ z2-#SA*e=;@Gfq;&76FS}qVlkh5^dPgLC2jz#Zdi@ zFu1B>10?{*2%W16XLW`3b5J4u+1XDQ8_nlIg+^={oQH|ef_iOuFiRFkKlUpuzB5Ub zda6kXZPjGdYGKCwYymT!a*HaznxhZRt9?;T_}^ihBLKFk@V~$|d)Quy>*-pa&sXBG zM!T;?MvC>WFwQA`&?O#{;}TR#d~E-271KE40bDm_8)0 zV8=Rl{Lyhk4W$1~4dfU5U#Nj}i0ZW)Oh45&m--%?a-zyJ?4aI~d7581&`Ph>o2e5` zo-@BgPP7i52h7Dt;gzf@o|3`B$y&HwO!^x&`Cr`p5{x%Wo?^)egk)z|E=tge!9J%yDo z;$C=+FK!GeoouSJAA8LN?ux5iXMX{GLh*^Khq~qcxxrH4$y|B1<=~q>m<`%-yINg^ ze`tqVohc<$*^8@Q9pE@iQaMP4qmxM)ON~swuY6Yzjm?SdH*wm(_)H?>uG-Y)xXVJp zNa|C@-orS^Jfe}9N}(|yxwO)YVe}4H>+&2j#!wQW4H&iL*1_a0V$KH#Ngt}V2X+lU zVG`7SH84l41i5k7tINx+FQ!J(6szmD{OI<)q&)VYYX{(Gus|K}v^H{E1D=HS8@J+9;=HrBQGHT<07fW9?EI9xIQ(-tO|$QA$K+ zoqVrWwDd_|Pq&1t^x5%GlbIc&gsG0;wlQiqY#DL#46JPBc13!fCt3~K7sXhq%?$KU z%6~BrZDlM}{+)R^Z4dla0rOD5AUkkKb`%(yC#i00qOsONd4v(1F>Js zv((@B*nb2_k35z{kz`?qMC!_b#Mts7NExEjI2J-jToL z8UQv18JG*?ls^rmr+vqxCs(u$!h@BPNkZwAi}xr+6>S9m^nl&oC^{#q&QCc5p(D@L zfo)*1Zo`%Q^Jj-b&N;~6Mze#0ZbEe(+&0%CrS~ta|85H%gaqKJo`p$GV_mtO5w_}X zo;or^FV{Iw>5W6(TWn<{mTam^Vwa0-0Hym$;B&gz^>*EE-q5B!l!z5vQ=Y17<%Mir zb2NwNN++Nty0&3hu6EnlA9oQ4fV)i_sw>rqzDzS16+8NnTL^0I?V5p2-aj*@=mRaC zj-#=61+PcmWQmboi{zvmldcw4kjU91diA2DZq3qV2-GRaNvz_44R|!)y$}lu)Cor3 zai$F{xnV`=F*^oq7;xa*=^Z%CzCKF_;GYlO0N$Ps}|!y9d+zdunB?V>{bwWs=LyUc65Fp z8`9go33`BF&6?*Y(F#3o4cJe21>9M&O!Z6?;d~CZMxz|C5pD23d^co`_?uzo?N_@l z?C=}V>TV3uy%6MIFjnx9A1FglDC9!0L{r9U9c{r2@6NL1*Fn|^I<*_TPkw)C9yWA} zg*m8S8S`C5ohBU)xDiL9L4llzmka|F^WXmL7cTbCp{8MAYwJ>-tIE2%yzB>zyx(9^ zPs_pJJ4kGIZEVWt=clxr-|Om`v*@JM2!~x2%m=HfyqVe3pIM5sc6`hOlpA^`4Fd&xy*#Su3 zGx*o$M?wrm@Ob&v&PDou>B_#f4KJ zZh3Q9O{fCB2A4xW8|}rZYKO=e&&#Dbpld6-Gfj^JB;@O)h*x)Jc!5Yor%Q;+QzoD? z06(D6?+U$GUX7Y_V>JRs=i=f4C^9{FB(jI@zB)ufK{?r)1^s4LGBUgFA#}J78-SW3 z$8(TvbiH$TzXF+;!-W3t!<8jcrM#Y&i_TJ2oC7_r52g&-jcLWj{1a9MA3vu5141SC zAmQHw$Abd^`~!dxvxy4WrOPWuZ9bVdZ(>uRY&N#gSsfsyk&7$_jPFkOcY$2i{m1wi z`1NlT6rxs^sQwQ@hK*kctkKrig~H~mg~i3qvbNtxnWnC&Ntu%e^g&vXRZl0KmT95O zBqYtzo(EQ^;>(YI)n%yzva}17s{}~^T5~^H(@}US>v#lU^YbbI13oKD*ePtMLQthd z6V!Rgd_W~Gm}v1BR9*HLo9Z<0@{0ZfHa$UMH59DiHuw8*etvJN`ZYNf1*my4=`8_} z7AKId0@rvk*EkD^_N{Gt^639v!0l1$nz!xHvnjT}VLO4x$ZhNsN=hFKx0SeP$uAr)vMIj{x!sxVFJ=Td5tOg%?~+ ztbdJY#kJB0Q9okrbXo8hpgbhx->QeCpp^6P;Y&={VoZbV31NA9dIZa@|5_P*NK0E7 zNR^T-ux+VpX&C`UFf%gBh>H57KgU9Oh=L*&ENz%z5bwCzxPyf$o7x1L z0bwM9>YAF-5eZM~%tWR7^daA3%R)H%HGC{41{U|5=dR}F^g#7IHaH;0m=U^O7srg=n4RxOicE*kzZ!!a3FSJ4C=SlLtJ0F zfXNmsP|pO7aexQWtgpp@(4_YDVDu~i77PG-Yy}7JL043O^u}bBhS;7S&Z5rCf6zOU zU=YEZdLc+;`rpb)?24h+xW)Y+2{WH9Ug=D66RdIHl=2>)=-|V_S$n?b2=vIOIP~<` z8fPKogpM1{QPIMt*h^QsLebic#wnKD%W4r?S`ob=T#-&Po(R+1QXB;!1 zd~Bgue7mCb1*l*#2uQjm*p4X8`#DLk4($v;+WUCK$D{wOLo63;2CAz0}VJ zjCDmhWKwb@C{uLQ!C_!O{re&9 z54hw*^5E@gnkBWTk_SNsX3eHKetjzYeIbIdERaZMRp^&9`J0{1<)ADSX+l1M6m4N# z81g_v^a~7zvFb(_Dmh~7_%;;reM0hDw9ZHt1cA$%=ODY0hud0&Ea|)`9$22zYhd+> z6ca3(J#4o-m~znwnp1Y#d|lY#p_W@JxXz!QX!G|WA>F{Ltn|b^NJ)Ln@-(oL!Vkyh zqJ`BD1Vp-ug_KO$M=*_-5Bh>1kb=Wu$8-lrBIKQ!t%jA9wmtn?ot(z`GBLB?_;4g% zC}w6UT|QbEqs1I|u3V`XFS9V5!|oc0kQjkNQbn?)g70#Pp4q!--nRd$)dCaWYAfIe zGD3-i$5?nv_5C-sizllNtPwS1i zg^nQGaW}$KnM=(Iz3#@GW6WvezDp$%!8&X1B$otuVM|RhzgXkysiUXUUU#WV1tZHV znuL-PXQv!%#X&1amF?-H71j^G#%nR?yR>G-=biQ4u@&=6Tea17RXXJ#jm)bGg^q4B zPM;SY7k5;jU;mhliJ2nMDUK_-+}ei^WZaF>J#Or4>oP2iTXk#CyS`qpQRUd;wqH4x z$TC_Q>+I2~JWZOdK55q9&hM!^)Zh?o@t`H_4ppE_MO=+uSZp^H8+$)fZmg}YuA=Z~ zvg-ER&o~n1{I$}ch@<<>BQEXNUU{C@+!vS_Rc+1@xL8>J5+iuOWLDMQU@U8e%!_GD z{Rf-rJg?IE^^3lnQ+cTy_+2`gj=qGqId^q0j9)N06gRs^*;B4E*X1Dc3G91{Yd5!{ zLdvfo`5Wt#`T*9!dsOTwm_L+SDCAfwtE&v7Melk)Q9+rL}(~Y16_?LB2 s=r1hS%U24&Ia<{3dfIQ^LAtvwxB&NrmWA@%C{ZAZi@XsofxPwlf5X6hzyJUM literal 0 HcmV?d00001 diff --git a/screens/node2_events.png b/screens/node2_events.png new file mode 100644 index 0000000000000000000000000000000000000000..36664cae59493ac3b3f8d1eaf37ad1e1e47ebb24 GIT binary patch literal 93634 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

Ha5uV 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 KSIHd%Y? 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®C9 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%0DOZ7sT*8zZQmPtQP(4 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 saky9qYJ_2zUx_`KFNjqQ`E zXSz1Gh08?!L{~0#Nyi8)2#YE>u1(S4%9`GCx+{^jkz~R%-%aOLw@O9n`ap@D#aQOx z8p<*0T`2gElXI)@M!|`U^2kFsR+hqw)6-K&`94wskG+-N`EQG6pa&UpWmXXS*&D~c zbFC#FrJN*^6doBi`dz|zBCb-v9JyUx<;tOJ^@?8meB~z@J(!c+FI-OyjP&EVT+)ZL z`P$sFEho9-D;y@i@17sUkAn?i(4ge?K7uSLR0J7TL*`&$q$Q+z3XsM=fo(#Hk5eL# z-tNu^Rr{fdnb0`0dsJtt?i(HjIs6(F!a-Ws$oJx$Wyi8ErY`BvZ2K!ZbGH~4r;{_S z)^s0k7DpsHMwkE?f?18r(mEjB!uST$616z4Ccx*J>iuvngyJzoC;E@oVepVk8DLY3 zt_4sX5xC(fzm^?H_c|VF)ng@n70+r5JIKP^H}YmPo%m*fl30|g2=y-Rb6GMXH4hjq zaU7PRq@=95s$tm5rD-*x!cM#olYIF6d+O4$Vb%Jv&GO{9hm-ve+7fr<^tr=1%I)iF zaMRyD%utG$%i*NrQ}k$(ZR`k#_3=TO%W4+R+(l*pC=qHj&yP39@=h5Ar?6b;zj^xT z5hAZf5YNX!%`?%_*jnFyc@WYM_Bu|Ibt*nIU)tcb8lws`lG!s|=CTxT=|WVdMCKKn z8nYRXyx4Zof}xi0KN%H-zB)~6jhAfy7+F|n2sW^>=nksJkHiNlh@JH*RaEHkxVV-A zL@j!l<#>I|k%#|h<7C=}W98wN&Bn|w1K%Z^c11;{=*NK8LctUji)cRUpg!TziB1|C zXnCCj1zFaXoP}}z+$m=f0Oa-*4KN)4*_PUncAdvii^{V4h7iGDM?X8{*pH{w;NzYZyc~% zt(s(Y_Nu!ctfrse$xi)7pWS|;EV+;W7$S390T;aD1sXt>UmsCSNo#N}qi>gJ<<&m_ z8mbf%jr;w*nl}yTz7P5O+u!;L)bEmz{{=(-GkA(bdGEQzv*%7dbNxgyA?a7Sus0w6 E57Ko3s{jB1 literal 0 HcmV?d00001 diff --git a/vagrant_scripts/dns.sh b/vagrant_scripts/dns.sh new file mode 100644 index 0000000..d8048ae --- /dev/null +++ b/vagrant_scripts/dns.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +yum install bind bind-utils -y + + +/bin/cp -f /tmp/dns/named.conf /etc/named.conf +/bin/cp -f /tmp/dns/forward.kuzniatsou /var/named/forward.kuzniatsou +/bin/cp -f /tmp/dns/reverse.kuzniatsou /var/named/reverse.kuzniatsou +#/bin/cp -f /tmp/dns/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf +#/bin/cp -f /tmp/dns/ifcfg-enp0s3 /etc/sysconfig/network-scripts/ifcfg-enp0s3 +#/bin/cp -f /tmp/dns/ifcfg-enp0s8 /etc/sysconfig/network-scripts/ifcfg-enp0s8 +echo -e 'DNS1="192.168.33.99"\nDNS2="10.0.2.3"\nPEERDNS="no"' >> /etc/sysconfig/network-scripts/ifcfg-enp0s3 + +systemctl enable named +#systemctl start named + +chgrp named -R /var/named +chown -v root:named /etc/named.conf +restorecon -rv /var/named +restorecon /etc/named.conf + + +#cp -f /tmp/dns/resolv.conf /etc/resolv.conf + +systemctl restart network +systemctl restart named + diff --git a/vagrant_scripts/master.sh b/vagrant_scripts/master.sh new file mode 100644 index 0000000..3342ca5 --- /dev/null +++ b/vagrant_scripts/master.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +rpm -Uvh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm +yum install -y puppet +echo -e 'DNS1="192.168.33.99"\nDNS2="10.0.2.3"\nPEERDNS="no"' >> /etc/sysconfig/network-scripts/ifcfg-enp0s3 + +/bin/cp /tmp/master/autosign.conf /etc/puppetlabs/puppet/ + +systemctl restart network +systemctl restart puppet +source ~/.bashrc +puppet resource service puppet ensure=running +puppet resource package puppetserver ensure=installed +puppet resource service puppetserver ensure=running +puppet module install puppetlabs-mysql --version 3.10.0 + +/bin/cp /tmp/master/site.pp /etc/puppetlabs/code/environments/production/manifests/site.pp \ No newline at end of file diff --git a/vagrant_scripts/node1.sh b/vagrant_scripts/node1.sh new file mode 100644 index 0000000..309f4ec --- /dev/null +++ b/vagrant_scripts/node1.sh @@ -0,0 +1,16 @@ +#!/bin/bash + + +echo -e 'DNS1="192.168.33.99"\nDNS2="10.0.2.3"\nPEERDNS="no"' >> /etc/sysconfig/network-scripts/ifcfg-enp0s3 +systemctl restart network + +rpm -Uvh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm +yum install -y puppet +/bin/cp /tmp/node1/puppet.conf /etc/puppetlabs/puppet/ + + +systemctl restart puppet +source ~/.bashrc +puppet resource service puppet ensure=running + +#puppet agent -tv --debug diff --git a/vagrant_scripts/node2.sh b/vagrant_scripts/node2.sh new file mode 100644 index 0000000..8435aca --- /dev/null +++ b/vagrant_scripts/node2.sh @@ -0,0 +1,16 @@ +#!/bin/bash + + +echo -e 'DNS1="192.168.33.99"\nDNS2="10.0.2.3"\nPEERDNS="no"' >> /etc/sysconfig/network-scripts/ifcfg-enp0s3 +systemctl restart network + +rpm -Uvh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm +yum install -y puppet +/bin/cp /tmp/node2/puppet.conf /etc/puppetlabs/puppet/ + + +systemctl restart puppet +source ~/.bashrc +puppet resource service puppet ensure=running + +#puppet agent -tv --debug diff --git a/vagrant_shares/dns/NetworkManager.conf b/vagrant_shares/dns/NetworkManager.conf new file mode 100644 index 0000000..05b2f01 --- /dev/null +++ b/vagrant_shares/dns/NetworkManager.conf @@ -0,0 +1,20 @@ +# Configuration file for NetworkManager. +# +# See "man 5 NetworkManager.conf" for details. +# +# The directory /etc/NetworkManager/conf.d/ can contain additional configuration +# snippets. Those snippets override the settings from this main file. +# +# The files within conf.d/ directory are read in asciibetical order. +# +# If two files define the same key, the one that is read afterwards will overwrite +# the previous one. + +[main] +plugins=ifcfg-rh +dns=no + +[logging] +#level=DEBUG +#domains=ALL +~ \ No newline at end of file diff --git a/vagrant_shares/dns/forward.kuzniatsou b/vagrant_shares/dns/forward.kuzniatsou new file mode 100644 index 0000000..59af03b --- /dev/null +++ b/vagrant_shares/dns/forward.kuzniatsou @@ -0,0 +1,19 @@ +$TTL 86400 +@ IN SOA dns.kuzniatsou.local. root.kuzniatsou.local. ( + 2011071001 ;Serial + 3600 ;Refresh + 1800 ;Retry + 604800 ;Expire + 86400 ;Minimum TTL +) +@ IN NS dns.kuzniatsou.local. +@ IN A 192.168.33.99 +@ IN A 192.168.33.90 +@ IN A 192.168.33.91 +dns IN A 192.168.33.99 +puppet IN A 192.168.33.90 +master IN A 192.168.33.90 +master1 IN A 192.168.33.190 +node1 IN A 192.168.33.91 +node2 IN A 192.168.33.92 +_x-puppet._tcp.kuzniatsou.local. IN SRV 0 5 8140 master1 \ No newline at end of file diff --git a/vagrant_shares/dns/ifcfg-enp0s3 b/vagrant_shares/dns/ifcfg-enp0s3 new file mode 100644 index 0000000..1fe9b5f --- /dev/null +++ b/vagrant_shares/dns/ifcfg-enp0s3 @@ -0,0 +1,20 @@ +# Generated by dracut initrd +NAME="enp0s3" +DEVICE="enp0s3" +ONBOOT="yes" +NETBOOT="yes" +UUID="f38ebe30-64e8-43ec-a866-7fd182f241e7" +IPV6INIT="yes" +BOOTPROTO="dhcp" +TYPE="Ethernet" +DEFROUTE="yes" +PEERROUTES="yes" +IPV4_FAILURE_FATAL="no" +IPV6_AUTOCONF="yes" +IPV6_DEFROUTE="yes" +IPV6_PEERDNS="yes" +IPV6_PEERROUTES="yes" +IPV6_FAILURE_FATAL="no" +DNS1="192.168.33.99" +DNS2="10.0.2.3" +PEERDNS="no" diff --git a/vagrant_shares/dns/ifcfg-enp0s8 b/vagrant_shares/dns/ifcfg-enp0s8 new file mode 100644 index 0000000..3c06868 --- /dev/null +++ b/vagrant_shares/dns/ifcfg-enp0s8 @@ -0,0 +1,12 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +NM_CONTROLLED=no +BOOTPROTO=none +ONBOOT=yes +IPADDR=192.168.33.99 +NETMASK=255.255.255.0 +DEVICE=enp0s8 +PEERDNS=no +DNS1="192.168.33.99" +DNS2="10.0.2.3" +#VAGRANT-END diff --git a/vagrant_shares/dns/named.conf b/vagrant_shares/dns/named.conf new file mode 100644 index 0000000..15661ac --- /dev/null +++ b/vagrant_shares/dns/named.conf @@ -0,0 +1,68 @@ +// +// named.conf +// +// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS +// server as a caching only nameserver (as a localhost DNS resolver only). +// +// See /usr/share/doc/bind*/sample/ for example named configuration files. +// + +options { + listen-on port 53 { 127.0.0.1; 192.168.33.99;}; ### Master DNS IP ### +# listen-on-v6 port 53 { ::1; }; + directory "/var/named"; + dump-file "/var/named/data/cache_dump.db"; + statistics-file "/var/named/data/named_stats.txt"; + memstatistics-file "/var/named/data/named_mem_stats.txt"; + allow-query { localhost; 192.168.33.0/24; }; ### IP Range ### + + /* + - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion. + - If you are building a RECURSIVE (caching) DNS server, you need to enable + recursion. + - If your recursive DNS server has a public IP address, you MUST enable access + control to limit queries to your legitimate users. Failing to do so will + cause your server to become part of large scale DNS amplification + attacks. Implementing BCP38 within your network would greatly + reduce such attack surface + */ + recursion no; + +// dnssec-enable yes; +// dnssec-validation yes; +// dnssec-lookaside auto; + + /* Path to ISC DLV key */ + bindkeys-file "/etc/named.iscdlv.key"; + + managed-keys-directory "/var/named/dynamic"; + + pid-file "/run/named/named.pid"; + session-keyfile "/run/named/session.key"; +}; + +logging { + channel default_debug { + file "data/named.run"; + severity dynamic; + }; +}; + +zone "." IN { + type hint; + file "named.ca"; +}; + +zone "kuzniatsou.local" IN { +type master; +file "forward.kuzniatsou"; +allow-update { none; }; +}; +zone "33.168.192.in-addr.arpa" IN { +type master; +file "reverse.kuzniatsou"; +allow-update { none; }; +}; + +include "/etc/named.rfc1912.zones"; +include "/etc/named.root.key"; \ No newline at end of file diff --git a/vagrant_shares/dns/reverse.kuzniatsou b/vagrant_shares/dns/reverse.kuzniatsou new file mode 100644 index 0000000..93521f8 --- /dev/null +++ b/vagrant_shares/dns/reverse.kuzniatsou @@ -0,0 +1,20 @@ +$TTL 86400 +@ IN SOA dns.kuzniatsou.local. root.kuzniatsou.local. ( + 2011071001 ;Serial + 3600 ;Refresh + 1800 ;Retry + 604800 ;Expire + 86400 ;Minimum TTL +) +@ IN NS dns.kuzniatsou.local. +@ IN PTR kuzniatsou.local. +dns IN A 192.168.33.99 +master IN A 192.168.33.90 +puppet IN A 192.168.33.90 +node1 IN A 192.168.33.91 +99 IN PTR dns.kuzniatsou.local. +90 IN PTR master.kuzniatsou.local. +190 IN PTR master1.kuzniatsou.local. +90 IN PTR puppet.kuzniatsou.local. +91 IN PTR node1.kuzniatsou.local. +92 IN PTR node2.kuzniatsou.local. diff --git a/vagrant_shares/master/autosign.conf b/vagrant_shares/master/autosign.conf new file mode 100644 index 0000000..f5cf77d --- /dev/null +++ b/vagrant_shares/master/autosign.conf @@ -0,0 +1 @@ +*.kuzniatsou.local \ No newline at end of file diff --git a/vagrant_shares/master/site.pp b/vagrant_shares/master/site.pp new file mode 100644 index 0000000..5cf11f7 --- /dev/null +++ b/vagrant_shares/master/site.pp @@ -0,0 +1,11 @@ +node node1.kuzniatsou.local { + include '::mysql::server' +} + +# Ensure that the database 'test_mdb' is created, the user 'test_user' with assigned privileges exists +mysql::db { 'test_mdb': + user => 'test_user', + password => 'password', + host => 'localhost', + grant => ['SELECT', 'UPDATE'], +} \ No newline at end of file diff --git a/vagrant_shares/node1/puppet.conf b/vagrant_shares/node1/puppet.conf new file mode 100644 index 0000000..e35f6f2 --- /dev/null +++ b/vagrant_shares/node1/puppet.conf @@ -0,0 +1,11 @@ +# This file can be used to override the default puppet settings. +# See the following links for more details on what settings are available: +# - https://docs.puppetlabs.com/puppet/latest/reference/config_important_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_about_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_file_main.html +# - https://docs.puppetlabs.com/puppet/latest/reference/configuration.html +[agent] + use_srv_records = true + srv_domain = kuzniatsou.local + runinterval = 60s + waitforcert = 20s diff --git a/vagrant_shares/node2/puppet.conf b/vagrant_shares/node2/puppet.conf new file mode 100644 index 0000000..be75b85 --- /dev/null +++ b/vagrant_shares/node2/puppet.conf @@ -0,0 +1,12 @@ +# This file can be used to override the default puppet settings. +# See the following links for more details on what settings are available: +# - https://docs.puppetlabs.com/puppet/latest/reference/config_important_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_about_settings.html +# - https://docs.puppetlabs.com/puppet/latest/reference/config_file_main.html +# - https://docs.puppetlabs.com/puppet/latest/reference/configuration.html +[agent] + use_srv_records = true + srv_domain = kuzniatsou.local + environment = prod + runinterval = 60s + waitforcert = 20s From 78e37fa04ae3c4aef5617e1cb55c11a11d09c82a Mon Sep 17 00:00:00 2001 From: ngkuznetsov Date: Mon, 17 Apr 2017 04:08:21 +0300 Subject: [PATCH 2/4] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f000ce..baadf4d 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -# lesson13 \ No newline at end of file +# lesson13 + +> **Table of contents:** + +> - [**environments**][1] - directory with 2 environments for node1 and node2 +> - [**manuals**][2] - dirctory with manuals +> - [**vagrant_scripts**][3] - Vagrant directory of scripts for provisioning VMs +> - [**vagrant_shares**][4] - Vagrant directory of shares for VMs +> - [**Vagrantfile**][5] - Vagrantfile. Provisioning dns, master, node1, node2 VMs +> - [**[agent]-node1-puppet.conf**][6] - configuration file of puppet on node1 +> - [**[agent]-node2-puppet.conf**][7] - configuration file of puppet on node2 +> - [**puppet.conf**][8] - configuration file of puppet on master +> - [**puppetdb.conf**][9] - configuration file of puppetdb + + +> **Manuals** directory has 2 options of installation puppetdb and puppexplorer: +> - [Installing_puppetdb_and_puppetexplorer_as_modules.txt][10] +> - [Manual_installing_puppetdb_and_puppetexplorer.txt][11] + + +### Report of the node2.kuzniatsou.local. +![alt text](screens/node2_events.png "node2") + +### Main dashboard of the master1.kuzniatsou.local. +![alt text](screens/master_main.png "master") + +[1]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/environments +[2]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/manuals +[3]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/vagrant_scripts +[4]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/vagrant_shares +[5]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/Vagrantfile +[6]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/%5Bagent%5D-node1-puppet.conf +[7]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/%5Bagent%5D-node2-puppet.conf +[8]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/puppet.conf +[9]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/puppetdb.conf +[10]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt +[11]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt From ee5ad317569ad7500702cd1ec00b6cdacd54dce6 Mon Sep 17 00:00:00 2001 From: mikalai_kuzniatsou Date: Mon, 17 Apr 2017 04:14:22 +0300 Subject: [PATCH 3/4] dist of puppetexplorer --- dist/app.js | 135 + dist/app/app.js | 70 + dist/app/config.js.example | 46 + dist/app/controllers/dashboard/dashboard.js | 78 + .../controllers/dashboard/dashboard.tpl.html | 114 + dist/app/controllers/events/events.js | 299 ++ dist/app/controllers/events/events.tpl.html | 129 + dist/app/controllers/facts/facts.js | 214 + dist/app/controllers/facts/facts.tpl.html | 31 + dist/app/controllers/menu/menu.js | 30 + dist/app/controllers/nodedetail/nodedetail.js | 118 + .../nodedetail/nodedetail.tpl.html | 71 + dist/app/controllers/nodelist/nodelist.js | 113 + .../controllers/nodelist/nodelist.tpl.html | 33 + dist/app/controllers/search/search.js | 32 + dist/app/css/style.css | 44 + dist/app/favicon.ico | Bin 0 -> 1150 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes .../fonts/glyphicons-halflings-regular.svg | 229 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes dist/app/index.html | 75 + dist/app/services/puppetdb/puppetdb.js | 165 + dist/config.js | 19 + dist/config.js.example | 46 + dist/controllers/dashboard/dashboard.tpl.html | 114 + dist/controllers/events/events.tpl.html | 129 + dist/controllers/facts/facts.tpl.html | 31 + .../nodedetail/nodedetail.tpl.html | 71 + dist/controllers/nodelist/nodelist.tpl.html | 33 + dist/css/bootstrap.min.css | 11 + dist/css/style.css | 44 + dist/favicon.ico | Bin 0 -> 1150 bytes dist/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes dist/fonts/glyphicons-halflings-regular.svg | 229 + dist/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes dist/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes dist/fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes dist/index.html | 75 + dist/lib/moment/moment.js | 4463 +++++++++++++++++ master1.kuzniatsou.local.conf | 34 + 42 files changed, 7325 insertions(+) create mode 100644 dist/app.js create mode 100644 dist/app/app.js create mode 100644 dist/app/config.js.example create mode 100644 dist/app/controllers/dashboard/dashboard.js create mode 100644 dist/app/controllers/dashboard/dashboard.tpl.html create mode 100644 dist/app/controllers/events/events.js create mode 100644 dist/app/controllers/events/events.tpl.html create mode 100644 dist/app/controllers/facts/facts.js create mode 100644 dist/app/controllers/facts/facts.tpl.html create mode 100644 dist/app/controllers/menu/menu.js create mode 100644 dist/app/controllers/nodedetail/nodedetail.js create mode 100644 dist/app/controllers/nodedetail/nodedetail.tpl.html create mode 100644 dist/app/controllers/nodelist/nodelist.js create mode 100644 dist/app/controllers/nodelist/nodelist.tpl.html create mode 100644 dist/app/controllers/search/search.js create mode 100644 dist/app/css/style.css create mode 100644 dist/app/favicon.ico create mode 100644 dist/app/fonts/glyphicons-halflings-regular.eot create mode 100644 dist/app/fonts/glyphicons-halflings-regular.svg create mode 100644 dist/app/fonts/glyphicons-halflings-regular.ttf create mode 100644 dist/app/fonts/glyphicons-halflings-regular.woff create mode 100644 dist/app/fonts/glyphicons-halflings-regular.woff2 create mode 100644 dist/app/index.html create mode 100644 dist/app/services/puppetdb/puppetdb.js create mode 100644 dist/config.js create mode 100644 dist/config.js.example create mode 100644 dist/controllers/dashboard/dashboard.tpl.html create mode 100644 dist/controllers/events/events.tpl.html create mode 100644 dist/controllers/facts/facts.tpl.html create mode 100644 dist/controllers/nodedetail/nodedetail.tpl.html create mode 100644 dist/controllers/nodelist/nodelist.tpl.html create mode 100644 dist/css/bootstrap.min.css create mode 100644 dist/css/style.css create mode 100644 dist/favicon.ico create mode 100644 dist/fonts/glyphicons-halflings-regular.eot create mode 100644 dist/fonts/glyphicons-halflings-regular.svg create mode 100644 dist/fonts/glyphicons-halflings-regular.ttf create mode 100644 dist/fonts/glyphicons-halflings-regular.woff create mode 100644 dist/fonts/glyphicons-halflings-regular.woff2 create mode 100644 dist/index.html create mode 100644 dist/lib/moment/moment.js create mode 100644 master1.kuzniatsou.local.conf diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 0000000..715ae8d --- /dev/null +++ b/dist/app.js @@ -0,0 +1,135 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oj;i=++j)panel=ref[i],panel.count=void 0,callback=function(panel){return function(count){return panel.count=count}},this.getNodeCount(panel.query,callback(panel));return this.$scope.panelWidth=Math.max(2,Math.floor(12/this.$scope.panels.length)),this.checkVersion()},_Class.prototype.getBean=function(name,scopeName,multiply,bean){return null==multiply&&(multiply=1),null==bean&&(bean="puppetlabs.puppetdb.query.population"),this.$scope[scopeName]=void 0,this.PuppetDB.getBean(bean+":type=default,name="+name).success(function(_this){return function(data){return _this.$scope[scopeName]=(angular.fromJson(data).Value*multiply).toLocaleString().replace(/^(.*\..).*/,"$1")}}(this)).error(function(data,status){if(0!==status)throw new Error("Could not fetch metric "+name+" from PuppetDB")})},_Class.prototype.getNodeCount=function(query,callback){return this.PuppetDB.parseAndQuery("nodes",query,null,{limit:1},function(data,total){return callback(total)})},_Class.prototype.setQuery=function(query){return this.$location.search("query",query),this.$location.path("/nodes")},_Class.prototype.checkVersion=function(){return this.PuppetDB.getVersion().success(function(data){var major,minor,patch;if(major=parseInt(data.version.split(".")[0],10),minor=parseInt(data.version.split(".")[1],10),patch=parseInt(data.version.split(".")[2],10),!(major>=3))throw new Error("This version of Puppet Explorer requires PuppetDB version 3.0.0+, you are running PuppetDB "+data.version)})},_Class}()); + +},{}],3:[function(require,module,exports){ +var bind=function(fn,me){return function(){return fn.apply(me,arguments)}};angular.module("app").controller("EventsCtrl",function(){function _Class($scope,$rootScope,$location,PuppetDB){this.$scope=$scope,this.$rootScope=$rootScope,this.$location=$location,this.PuppetDB=PuppetDB,this.drawChart=bind(this.drawChart,this),this.fetchStatusCounts=bind(this.fetchStatusCounts,this),this.fetchResourceCounts=bind(this.fetchResourceCounts,this),this.fetchContainingClasses=bind(this.fetchContainingClasses,this),this.fetchEvents=bind(this.fetchEvents,this),this.reset=bind(this.reset,this),this.setFields=bind(this.setFields,this),this.$scope.$on("queryChange",this.reset),this.$scope.$on("pageChange",this.fetchEvents),this.$scope.$on("$locationChangeSuccess",this.setFields),this.$scope.$on("filterChange",this.reset),this.$scope.perPage=50,this.mode={},this.setFields()}return _Class.prototype.setFields=function(event,exclude){return this.mode.current=this.$location.search().mode||"latest",this.mode[this.mode.current]=!0,this.$scope.reportHash=this.$location.search().report,this.$scope.dateFrom=this.$location.search().date_from||moment.utc().format("YYYY-MM-DD"),this.$scope.dateTo=this.$location.search().date_to||moment.utc().format("YYYY-MM-DD"),this.reset(event,exclude)},_Class.prototype.reset=function(event,exclude){return event&&"pageChange"===event.name&&(console.log(event),this.setFields()),this.$location.search("page",null),this.$scope.numItems=void 0,this.fetchEvents(),"containing_class"!==exclude&&this.fetchContainingClasses(),"resource_type"!==exclude&&this.fetchResourceCounts(),"status"!==exclude?this.fetchStatusCounts():void 0},_Class.prototype.setMode=function(mode){return this.mode.current=mode,this.$location.search("mode",mode),this.setFilters()},_Class.prototype.setFilters=function(){return this.$location.search("report",this.$scope.reportHash),this.$location.search("date_from",moment(this.$scope.dateFrom).format("YYYY-MM-DD")),this.$location.search("date_to",moment(this.$scope.dateTo).format("YYYY-MM-DD")),this.$rootScope.$broadcast("filterChange")},_Class.prototype.createEventQuery=function(exclude){var cc,moment,query;return null==exclude&&(exclude=!1),query=["and"],"latest"===this.mode.current?query.push(["=","latest_report?",!0]):"report"===this.mode.current?query.push(["=","report",this.$scope.reportHash]):(moment=require("moment"),query.push([">","timestamp",moment.utc(this.$scope.dateFrom).toISOString()]),query.push(["<","timestamp",moment.utc(this.$scope.dateTo).add(1,"days").toISOString()])),null!=this.$location.search().containing_class&&"containing_class"!==exclude&&(cc=this.$location.search().containing_class,"none"===cc&&(cc=null),query.push(["=","containing_class",cc])),null!=this.$location.search().resource_type&&"resource_type"!==exclude&&query.push(["=","resource_type",this.$location.search().resource_type]),null!=this.$location.search().status&&"status"!==exclude&&query.push(["=","status",this.$location.search().status.toLowerCase()]),query},_Class.prototype.fetchEvents=function(){return this.events=void 0,this.PuppetDB.parseAndQuery("events",this.$location.search().query,this.createEventQuery(),{offset:this.$scope.perPage*((this.$location.search().page||1)-1),limit:this.$scope.perPage,order_by:angular.toJson([{field:"timestamp",order:"desc"}])},function(_this){return function(data,total){return _this.$scope.numItems=total,_this.events=data}}(this))},_Class.prototype.toggleRow=function(event){return event.show=!event.show},_Class.prototype.fetchContainingClasses=function(){return this.drawChart("containingChart","Containing class"),this.PuppetDB.parseAndQuery("event-counts",this.$location.search().query,this.createEventQuery("containing_class"),{summarize_by:"containing_class"},function(_this){return function(data,total){var chartData,item,j,key,len,value;for(chartData=[],j=0,len=data.length;len>j;j++)item=data[j],key=item.subject.title||"none",value=item.failures+item.successes+item.noops+item.skips,chartData.push([key,value]);return _this.drawChart("containingChart","Containing class",chartData)}}(this))},_Class.prototype.fetchResourceCounts=function(){return this.drawChart("resourceChart","Resource"),this.PuppetDB.parseAndQuery("event-counts",this.$location.search().query,this.createEventQuery("resource_type"),{summarize_by:"resource"},function(_this){return function(data,total){var chartData,item,j,key,len,name1,types,value;for(chartData=[],types={},j=0,len=data.length;len>j;j++)item=data[j],types[name1=item.subject.type]||(types[name1]=0),types[item.subject.type]+=item.failures+item.successes+item.noops+item.skips;for(key in types)value=types[key],chartData.push([key,value]);return _this.drawChart("resourceChart","Resource type",chartData)}}(this))},_Class.prototype.fetchStatusCounts=function(){return this.drawChart("statusChart","Event status"),this.PuppetDB.parseAndQuery("aggregate-event-counts",this.$location.search().query,this.createEventQuery("status"),{summarize_by:"resource"},function(_this){return function(data,total){var chartData;return chartData=[["Success",data.successes],["Skipped",data.skips],["Failure",data.failures],["Noop",data.noops]],_this.drawChart("statusChart","Event status",chartData)}}(this))},_Class.prototype.drawChart=function(name,title,data){var colors,enableInteractivity,sliceText;return data?(colors=["#A1CF64","#FFCC66","#D95C5C","#6ECFF5","#564F8A","#00B5AD","#F05940"],sliceText="percent",enableInteractivity=!0):(colors=["#BBBBBB"],sliceText="label",data=[["Loading",1]],enableInteractivity=!1),this.$scope[name]={type:"PieChart",options:{backgroundColor:"transparent",colors:colors,pieSliceText:sliceText,enableInteractivity:enableInteractivity,height:250,chartArea:{width:"100%",height:"85%"}},data:[["Type","Number"]].concat(data)},this.$rootScope.$broadcast("resizeMsg")},_Class.prototype.color=function(status){switch(status){case"success":return"success";case"noop":return"text-muted";case"failure":return"danger";case"skipped":return"warning";default:return""}},_Class.prototype.onChartSelect=function(param,data,item){return item?this.$location.search(param,data[item.row+1][0]):this.$location.search(param,null),this.$rootScope.$broadcast("filterChange",param)},_Class.prototype.setChartSelection=function(param,data,chart){var i,j,len,r,row,selected;if(chart.getOptions().enableInteractivity&&(selected=this.$location.search()[param],null!=selected)){for(row=null,i=j=0,len=data.length;len>j;i=++j)r=data[i],r[0]===selected&&(row=i);if(row)return chart.getChart().setSelection([{row:row-1}])}},_Class.prototype.toggleDatePicker=function($event,name){return $event.preventDefault(),$event.stopPropagation(),this.$scope[name]=!this.$scope[name]},_Class.prototype.selectReport=function(report){return this.$scope.reportHash=report,this.setMode("report")},_Class}()); + +},{"moment":26}],4:[function(require,module,exports){ +var bind=function(fn,me){return function(){return fn.apply(me,arguments)}},indexOf=[].indexOf||function(item){for(var i=0,l=this.length;l>i;i++)if(i in this&&this[i]===item)return i;return-1};angular.module("app").controller("FactsCtrl",function(){function _Class($scope,$rootScope,$location,PuppetDB){this.$scope=$scope,this.$rootScope=$rootScope,this.$location=$location,this.PuppetDB=PuppetDB,this.reloadCharts=bind(this.reloadCharts,this),this.syncCharts=bind(this.syncCharts,this),this.charts={},this.getFactNames(),this.$scope.$on("queryChange",this.reloadCharts),this.$scope.$on("$locationChangeSuccess",this.syncCharts),this.syncCharts()}return _Class.prototype.factPathToString=function(path){var str;return str=path.map(function(pathComponent){return"number"==typeof pathComponent?String(pathComponent):/^[.\d]+$/.test(pathComponent)?'"'+pathComponent+'"':pathComponent}).join(".")},_Class.prototype.factPathFromString=function(name){return name.split(".").map(function(pathComponent){return/^\d+$/.test(pathComponent)?parseInt(pathComponent,10):'"'===pathComponent[0]?pathComponent.slice(1,-1):pathComponent})},_Class.prototype.chartList=function(){var ref;return(null!=(ref=this.$location.search().facts)?ref.split(","):void 0)||[]},_Class.prototype.chartActive=function(chart){return-1!==this.chartList().indexOf(this.factPathToString(chart))},_Class.prototype.syncCharts=function(){var chart,chartList,i,j,len,len1,ref,results;for(chartList=this.chartList(),ref=Object.keys(this.charts),i=0,len=ref.length;len>i;i++)chart=ref[i],indexOf.call(chartList,chart)<0&&delete this.charts[chart];for(results=[],j=0,len1=chartList.length;len1>j;j++)chart=chartList[j],indexOf.call(Object.keys(this.charts),chart)<0?results.push(this.addChart(this.factPathFromString(chart))):results.push(void 0);return results},_Class.prototype.reloadCharts=function(){var chart,chartName,ref,results;ref=this.charts,results=[];for(chartName in ref)chart=ref[chartName],results.push(this.addChart(chart.factPath));return results},_Class.prototype.getFactNames=function(){return this.PuppetDB.query("fact-paths",{order_by:angular.toJson([{field:"path",order:"asc"}])}).success(function(_this){return function(data,status,headers,config){var fact;return _this.factPaths=function(){var i,len,ref,results;for(ref=angular.fromJson(data),results=[],i=0,len=ref.length;len>i;i++)fact=ref[i],"_"!==fact.path[0][0]&&results.push(fact.path);return results}()}}(this)).error(function(data,status,headers,config){throw new Error(data||"Fetching fact names failed")})},_Class.prototype.toggleChart=function(fact){return this.chartActive(fact)?this.deleteChart(this.factPathToString(fact)):this.addChart(fact)},_Class.prototype.addChart=function(fact){var facts,ref;return facts=this.chartList(),ref=this.factPathToString(fact),indexOf.call(facts,ref)<0&&(facts.push(this.factPathToString(fact)),this.$location.search("facts",facts.join(","))),this.charts[this.factPathToString(fact)]={factPath:fact,type:"PieChart",options:{backgroundColor:"transparent",width:450,height:300,chartArea:{width:"100%",height:"100%",left:10,top:20},colors:["#BBBBBB"],pieSliceText:"label",enableInteractivity:!1},data:[["Value","Number"],["Loading",1]]},this.PuppetDB.parseAndQuery("fact-contents",this.$location.search().query,["=","path",fact],{},function(_this){return function(data,total){return _this.setChartData(fact,data)}}(this))},_Class.prototype.deleteChart=function(fact){var facts;return facts=this.chartList(),facts.splice(facts.indexOf(fact),1),this.$location.search("facts",facts.join(",")||null),delete this.charts[fact]},_Class.prototype.setChartData=function(fact,data){var f,factString,i,k,len,quotedVal,v,values;for(factString=this.factPathToString(fact),this.charts[factString].options.colors=["#A1CF64","#FFCC66","#D95C5C","#6ECFF5","#564F8A","#00B5AD","#F05940"],this.charts[factString].options.pieSliceText="percent",this.charts[factString].enableInteractivity=!0,this.charts[factString].data=[["Value","Number"]],values={},i=0,len=data.length;len>i;i++)f=data[i],quotedVal="string"==typeof f.value?'"'+f.value.replace('"','\\"')+'"':""+f.value,values[quotedVal]=values[quotedVal]+1||1;for(k in values)v=values[k],this.charts[factString].data.push([k,v]);return this.$rootScope.$broadcast("resizeMsg")},_Class.prototype.chartSelect=function(fact,item){var query,value;return value=this.charts[fact].data[item.row+1][0],query=this.$location.search().query,query?this.$location.search("query","("+query+") and "+fact+"="+value):this.$location.search("query",fact+"="+value)},_Class}()); + +},{}],5:[function(require,module,exports){ +angular.module("app").controller("MenuCtrl",function(){function _Class($scope,$rootScope,$location,PuppetDB){this.$scope=$scope,this.$rootScope=$rootScope,this.$location=$location,this.PuppetDB=PuppetDB,this.$scope.server=this.PuppetDB.server(),this.servers=this.PuppetDB.servers}return _Class.prototype.currentView=function(){return this.$location.path().split("/")[1]},_Class.prototype.view=function(path){return path?(this.$location.path(path),this.$location.search("page",null)):this.$location.path().split("/")[1]},_Class.prototype.setServer=function(server){return this.PuppetDB.server(server),this.$rootScope.$broadcast("queryChange")},_Class}()); + +},{}],6:[function(require,module,exports){ +var bind=function(fn,me){return function(){return fn.apply(me,arguments)}};angular.module("app").controller("NodeDetailCtrl",function(){function _Class($location,$routeParams,$scope,PuppetDB){this.$location=$location,this.$routeParams=$routeParams,this.$scope=$scope,this.PuppetDB=PuppetDB,this.fetchFacts=bind(this.fetchFacts,this),this.fetchReports=bind(this.fetchReports,this),this.reset=bind(this.reset,this),this.node=this.$routeParams.node,this.$scope.$on("pageChange",this.fetchReports),this.$scope.perPage=10,this.reset()}return _Class.prototype.reset=function(){return this.reports=void 0,this.$location.search("page",null),this.$scope.numItems=void 0,this.fetchReports(),this.fetchFacts()},_Class.prototype.fetchReports=function(){return this.reports=this.PuppetDB.parseAndQuery("reports",this.$location.search().query,["=","certname",this.node],{order_by:angular.toJson([{field:"end_time",order:"desc"}]),offset:this.$scope.perPage*((this.$location.search().page||1)-1),limit:this.$scope.perPage},function(_this){return function(data,total){return _this.reports=data,_this.$scope.numItems=total,_this.reports.forEach(function(report){return _this.PuppetDB.parseAndQuery("event-counts",null,["=","report",report.hash],{summarize_by:"certname"},function(data,total){return report.events=data[0]})})}}(this))},_Class.prototype.fetchFacts=function(){return this.PuppetDB.parseAndQuery("fact-contents",null,["=","certname",this.node],{order_by:angular.toJson([{field:"name",order:"asc"}])},function(_this){return function(data,total){return _this.facts=data.filter(function(fact){return"_"!==fact.name[0]}).map(function(fact){return fact.value=String(fact.value).replace(/(.{25})/g,"​$1"),fact})}}(this))},_Class.prototype.importantFacts=function(){return this.facts.filter(function(fact){return-1!==NODE_FACTS.indexOf(fact.name)}).sort(function(a,b){return NODE_FACTS.indexOf(a.name)-NODE_FACTS.indexOf(b.name)})},_Class.prototype.status=function(report){var ref,ref1,ref2,ref3;return void 0===report?"glyphicon-refresh spin":null===report?"glyphicon-question-sign":"failed"===report.status?"glyphicon-warning-sign text-danger":"changed"===report.status?"glyphicon-exclamation-sign text-success":(null!=(ref=report.events)?ref.failures:void 0)>0?"glyphicon-warning-sign text-danger":(null!=(ref1=report.events)?ref1.skips:void 0)>0?"glyphicon-exclamation-sign text-warning":(null!=(ref2=report.events)?ref2.noops:void 0)>0?"glyphicon-exclamation-sign text-info":(null!=(ref3=report.events)?ref3.successes:void 0)>0?"glyphicon-exclamation-sign text-success":"glyphicon-ok-sign text-muted"},_Class.prototype.selectReport=function(report){return this.$location.search("mode","report"),this.$location.search("report",report),this.$location.path("/events")},_Class}()); + +},{}],7:[function(require,module,exports){ +var bind=function(fn,me){return function(){return fn.apply(me,arguments)}};angular.module("app").controller("NodeListCtrl",function(){function _Class($location,$scope,PuppetDB){this.$location=$location,this.$scope=$scope,this.PuppetDB=PuppetDB,this.selectNode=bind(this.selectNode,this),this.fetchNodeStatus=bind(this.fetchNodeStatus,this),this.fetchNodeEventCount=bind(this.fetchNodeEventCount,this),this.fetchNodes=bind(this.fetchNodes,this),this.reset=bind(this.reset,this),this.$scope.$on("pageChange",this.fetchNodes),this.$scope.$on("queryChange",this.reset),this.$scope.perPage=50,this.reset()}return _Class.prototype.reset=function(){return this.$location.search("page",null),this.$scope.numItems=void 0,this.fetchNodes()},_Class.prototype.fetchNodes=function(){return this.nodes=void 0,this.PuppetDB.parseAndQuery("nodes",this.$location.search().query,null,{offset:this.$scope.perPage*((this.$location.search().page||1)-1),limit:this.$scope.perPage,order_by:angular.toJson([{field:"certname",order:"asc"}])},function(_this){return function(data,total){var i,len,node,ref;for(_this.$scope.numItems=total,_this.nodes=data,ref=_this.nodes,i=0,len=ref.length;len>i;i++)node=ref[i],_this.fetchNodeEventCount(node),_this.fetchNodeStatus(node);return null!=_this.$location.search().node?_this.fetchSelectedNode():void 0}}(this))},_Class.prototype.fetchNodeEventCount=function(node){return this.PuppetDB.parseAndQuery("event-counts",null,["and",["=","certname",node.certname],["=","latest_report?",!0]],{summarize_by:"certname",order_by:angular.toJson([{field:"certname",order:"asc"}])},function(node){return function(data,total){return data.length?node.events=data[0]:node.events={failures:0,skips:0,noops:0,successes:0}}}(node))},_Class.prototype.fetchNodeStatus=function(node){return this.PuppetDB.parseAndQuery("reports",null,["=","certname",node.certname],{order_by:angular.toJson([{field:"receive_time",order:"desc"}]),limit:1},function(node){return function(data,total){return data.length?node.report=data[0]:node.report=null}}(node))},_Class.prototype.selectNode=function(node){return this.$location.path("/node/"+node.certname)},_Class.prototype.showEvents=function(node){return this.$location.search("query",'"'+node+'"'),this.$location.path("/events")},_Class.prototype.nodeStatus=function(node){var ref,ref1,ref2,ref3;return void 0===node.report?"glyphicon-refresh spin":null===node.report?"glyphicon-question-sign":"failed"===node.report.status?"glyphicon-warning-sign text-danger":"changed"===node.report.status?"glyphicon-exclamation-sign text-success":(null!=(ref=node.events)?ref.failures:void 0)>0?"glyphicon-warning-sign text-danger":(null!=(ref1=node.events)?ref1.skips:void 0)>0?"glyphicon-exclamation-sign text-warning":(null!=(ref2=node.events)?ref2.noops:void 0)>0?"glyphicon-exclamation-sign text-info":(null!=(ref3=node.events)?ref3.successes:void 0)>0?"glyphicon-exclamation-sign text-success":"glyphicon-ok-sign text-muted"},_Class.prototype.nodeUnresponsive=function(node){var hours;return null==node.report_timestamp?!0:(hours="undefined"!=typeof UNRESPONSIVE_HOURS&&null!==UNRESPONSIVE_HOURS?UNRESPONSIVE_HOURS:2,moment(node.report_timestamp).isBefore(moment.utc().subtract(hours,"hours")))},_Class}()); + +},{}],8:[function(require,module,exports){ +angular.module("app").controller("SearchCtrl",function(){function _Class($scope,$rootScope,$location){this.$scope=$scope,this.$rootScope=$rootScope,this.$location=$location,this.$scope.query=this.$location.search().query,this.$scope.$on("$locationChangeSuccess",function(_this){return function(event){var old;return old=_this.$scope.query,_this.$scope.query=_this.$location.search().query,old!==_this.$scope.query?_this.$rootScope.$broadcast("queryChange",{query:_this.$scope.query}):void 0}}(this)),this.$rootScope.$on("queryChange",function(_this){return function(){return _this.$location.search("containing_class",null),_this.$location.search("resource_type",null),_this.$location.search("status",null),_this.$location.search("node",null)}}(this))}return _Class.prototype.submit=function(){return this.$location.search("query",this.$scope.query),this.$rootScope.$broadcast("queryChange",{query:this.$scope.query}),"/dashboard"===this.$location.path()?this.$location.path("/nodes"):void 0},_Class}()); + +},{}],9:[function(require,module,exports){ +var bind=function(fn,me){return function(){return fn.apply(me,arguments)}};angular.module("app").factory("PuppetDB",function($http,$location,$q){var PuppetDB;return new(PuppetDB=function(){function PuppetDB(){this.cancel=bind(this.cancel,this),this.servers=PUPPETDB_SERVERS.map(function(srv){return srv[0]}),this.puppetdbquery=require("node-puppetdbquery"),this.canceller=$q.defer()}return PuppetDB.prototype.apiVersion="v4",PuppetDB.prototype.server=function(server){return server?$location.search("server",server):$location.search().server||PUPPETDB_SERVERS[0][0]},PuppetDB.prototype.serverUrl=function(){var i,len,server;for(i=0,len=PUPPETDB_SERVERS.length;len>i;i++)if(server=PUPPETDB_SERVERS[i],server[0]===this.server())return server[1]},PuppetDB.prototype.serverConfig=function(){var i,len,server;for(i=0,len=PUPPETDB_SERVERS.length;len>i;i++)if(server=PUPPETDB_SERVERS[i],server[0]===this.server())return server[2]?server[2]:{}},PuppetDB.prototype.parse=function(query){return query?this.puppetdbquery.parse(query):null},PuppetDB.prototype.query=function(endpoint,params){var config;return null==params&&(params={}),config=this.serverConfig(),config.params=params,config.timeout=this.canceller.promise,$http.get(this.serverUrl()+"/pdb/query/"+this.apiVersion+"/"+endpoint,config)},PuppetDB.prototype.parseAndQuery=function(endpoint,nodeQuery,additionalQuery,params,success){var query;return null==params&&(params={}),query=nodeQuery?this.parse(nodeQuery):null,additionalQuery?query=query?["and",query,additionalQuery]:additionalQuery:query||("reports"===endpoint||"events"===endpoint||"event-count"===endpoint||"aggregate-event-count"===endpoint)&&(query=[">","timestamp","1970-01-01T00:00:00.000Z"]),params.query=angular.toJson(query),("nodes"===endpoint||"reports"===endpoint||"events"===endpoint||"facts"===endpoint)&&(params.include_total=!0),this.query(endpoint,params).success(function(data,status,headers,config){return success(angular.fromJson(data),headers("X-Records"))}).error(function(data,status,headers,config){if(0!==status)throw new Error(data||"Failed to fetch "+endpoint)})},PuppetDB.prototype.cancel=function(){return this.canceller.resolve("user cancelled"),this.canceller=$q.defer()},PuppetDB.prototype.getBean=function(name){var config;return config=this.serverConfig(),config.timeout=this.canceller.promise,$http.get(this.serverUrl()+"/metrics/v1/mbeans/"+name,config)},PuppetDB.prototype.getVersion=function(){var config;return config=this.serverConfig(),config.timeout=this.canceller.promise,$http.get(this.serverUrl()+"/pdb/meta/v1/version",config)},PuppetDB}())}); + +},{"node-puppetdbquery":29}],10:[function(require,module,exports){ +!function(window,angular,undefined){"use strict";function assertArg(arg,name,reason){if(!arg)throw ngMinErr("areq","Argument '{0}' is {1}",name||"?",reason||"required");return arg}function mergeClasses(a,b){return a||b?a?b?(isArray(a)&&(a=a.join(" ")),isArray(b)&&(b=b.join(" ")),a+" "+b):a:b:""}function packageStyles(options){var styles={};return options&&(options.to||options.from)&&(styles.to=options.to,styles.from=options.from),styles}function pendClasses(classes,fix,isPrefix){var className="";return classes=isArray(classes)?classes:classes&&isString(classes)&&classes.length?classes.split(/\s+/):[],forEach(classes,function(klass,i){klass&&klass.length>0&&(className+=i>0?" ":"",className+=isPrefix?fix+klass:klass+fix)}),className}function removeFromArray(arr,val){var index=arr.indexOf(val);val>=0&&arr.splice(index,1)}function stripCommentsFromElement(element){if(element instanceof jqLite)switch(element.length){case 0:return[];case 1:if(element[0].nodeType===ELEMENT_NODE)return element;break;default:return jqLite(extractElementNode(element))}return element.nodeType===ELEMENT_NODE?jqLite(element):void 0}function extractElementNode(element){if(!element[0])return element;for(var i=0;i=0)&&(val=parseMaxTime(val)),0===val&&(val=null),styles[actualStyleName]=val}}),styles}function parseMaxTime(str){var maxValue=0,values=str.split(/\s*,\s*/);return forEach(values,function(value){"s"==value.charAt(value.length-1)&&(value=value.substring(0,value.length-1)),value=parseFloat(value)||0,maxValue=maxValue?Math.max(value,maxValue):value}),maxValue}function truthyTimingValue(val){return 0===val||null!=val}function getCssTransitionDurationStyle(duration,applyOnlyDuration){var style=TRANSITION_PROP,value=duration+"s";return applyOnlyDuration?style+=DURATION_KEY:value+=" linear all",[style,value]}function createLocalCacheLookup(){var cache=Object.create(null);return{flush:function(){cache=Object.create(null)},count:function(key){var entry=cache[key];return entry?entry.total:0},get:function(key){var entry=cache[key];return entry&&entry.value},put:function(key,value){cache[key]?cache[key].total++:cache[key]={total:1,value:value}}}}var TRANSITION_PROP,TRANSITIONEND_EVENT,ANIMATION_PROP,ANIMATIONEND_EVENT,noop=angular.noop,extend=angular.extend,jqLite=angular.element,forEach=angular.forEach,isArray=angular.isArray,isString=angular.isString,isObject=angular.isObject,isUndefined=angular.isUndefined,isDefined=angular.isDefined,isFunction=angular.isFunction,isElement=angular.isElement,ELEMENT_NODE=1,ADD_CLASS_SUFFIX="-add",REMOVE_CLASS_SUFFIX="-remove",EVENT_CLASS_PREFIX="ng-",ACTIVE_CLASS_SUFFIX="-active",NG_ANIMATE_CLASSNAME="ng-animate",NG_ANIMATE_CHILDREN_DATA="$$ngAnimateChildren",CSS_PREFIX="";window.ontransitionend===undefined&&window.onwebkittransitionend!==undefined?(CSS_PREFIX="-webkit-",TRANSITION_PROP="WebkitTransition",TRANSITIONEND_EVENT="webkitTransitionEnd transitionend"):(TRANSITION_PROP="transition",TRANSITIONEND_EVENT="transitionend"),window.onanimationend===undefined&&window.onwebkitanimationend!==undefined?(CSS_PREFIX="-webkit-",ANIMATION_PROP="WebkitAnimation",ANIMATIONEND_EVENT="webkitAnimationEnd animationend"):(ANIMATION_PROP="animation",ANIMATIONEND_EVENT="animationend");var DURATION_KEY="Duration",PROPERTY_KEY="Property",DELAY_KEY="Delay",TIMING_KEY="TimingFunction",ANIMATION_ITERATION_COUNT_KEY="IterationCount",ANIMATION_PLAYSTATE_KEY="PlayState",SAFE_FAST_FORWARD_DURATION_VALUE=9999,ANIMATION_DELAY_PROP=ANIMATION_PROP+DELAY_KEY,ANIMATION_DURATION_PROP=ANIMATION_PROP+DURATION_KEY,TRANSITION_DELAY_PROP=TRANSITION_PROP+DELAY_KEY,TRANSITION_DURATION_PROP=TRANSITION_PROP+DURATION_KEY,$$AnimateChildrenDirective=[function(){return function(scope,element,attrs){var val=attrs.ngAnimateChildren;angular.isString(val)&&0===val.length?element.data(NG_ANIMATE_CHILDREN_DATA,!0):attrs.$observe("ngAnimateChildren",function(value){value="on"===value||"true"===value,element.data(NG_ANIMATE_CHILDREN_DATA,value)})}}],ONE_SECOND=1e3,ELAPSED_TIME_MAX_DECIMAL_PLACES=3,CLOSING_TIME_BUFFER=1.5,DETECT_CSS_PROPERTIES={transitionDuration:TRANSITION_DURATION_PROP,transitionDelay:TRANSITION_DELAY_PROP,transitionProperty:TRANSITION_PROP+PROPERTY_KEY,animationDuration:ANIMATION_DURATION_PROP,animationDelay:ANIMATION_DELAY_PROP,animationIterationCount:ANIMATION_PROP+ANIMATION_ITERATION_COUNT_KEY},DETECT_STAGGER_CSS_PROPERTIES={transitionDuration:TRANSITION_DURATION_PROP,transitionDelay:TRANSITION_DELAY_PROP,animationDuration:ANIMATION_DURATION_PROP,animationDelay:ANIMATION_DELAY_PROP},$AnimateCssProvider=["$animateProvider",function($animateProvider){var gcsLookup=createLocalCacheLookup(),gcsStaggerLookup=createLocalCacheLookup();this.$get=["$window","$$jqLite","$$AnimateRunner","$timeout","$$forceReflow","$sniffer","$$rAF",function($window,$$jqLite,$$AnimateRunner,$timeout,$$forceReflow,$sniffer,$$rAF){function gcsHashFn(node,extraClasses){var KEY="$$ngAnimateParentKey",parentNode=node.parentNode,parentID=parentNode[KEY]||(parentNode[KEY]=++parentCounter);return parentID+"-"+node.getAttribute("class")+"-"+extraClasses}function computeCachedCssStyles(node,className,cacheKey,properties){var timings=gcsLookup.get(cacheKey);return timings||(timings=computeCssStyles($window,node,properties),"infinite"===timings.animationIterationCount&&(timings.animationIterationCount=1)),gcsLookup.put(cacheKey,timings),timings}function computeCachedCssStaggerStyles(node,className,cacheKey,properties){var stagger;if(gcsLookup.count(cacheKey)>0&&(stagger=gcsStaggerLookup.get(cacheKey),!stagger)){var staggerClassName=pendClasses(className,"-stagger");$$jqLite.addClass(node,staggerClassName),stagger=computeCssStyles($window,node,properties),stagger.animationDuration=Math.max(stagger.animationDuration,0),stagger.transitionDuration=Math.max(stagger.transitionDuration,0),$$jqLite.removeClass(node,staggerClassName),gcsStaggerLookup.put(cacheKey,stagger)}return stagger||{}}function waitUntilQuiet(callback){cancelLastRAFRequest&&cancelLastRAFRequest(),rafWaitQueue.push(callback),cancelLastRAFRequest=$$rAF(function(){cancelLastRAFRequest=null,gcsLookup.flush(),gcsStaggerLookup.flush();for(var pageWidth=$$forceReflow(),i=0;i0,flags.hasAnimations=timings.animationDuration>0}if(flags.applyTransitionDelay||flags.applyAnimationDelay){relativeDelay="boolean"!=typeof options.delay&&truthyTimingValue(options.delay)?parseFloat(options.delay):relativeDelay,maxDelay=Math.max(relativeDelay,0);var delayStyle;flags.applyTransitionDelay&&(timings.transitionDelay=relativeDelay,delayStyle=getCssDelayStyle(relativeDelay),temporaryStyles.push(delayStyle),node.style[delayStyle[0]]=delayStyle[1]),flags.applyAnimationDelay&&(timings.animationDelay=relativeDelay,delayStyle=getCssDelayStyle(relativeDelay,!0),temporaryStyles.push(delayStyle),node.style[delayStyle[0]]=delayStyle[1])}if(maxDelayTime=maxDelay*ONE_SECOND,maxDurationTime=maxDuration*ONE_SECOND,options.easing){var easeProp,easeVal=options.easing;flags.hasTransitions&&(easeProp=TRANSITION_PROP+TIMING_KEY,temporaryStyles.push([easeProp,easeVal]),node.style[easeProp]=easeVal),flags.hasAnimations&&(easeProp=ANIMATION_PROP+TIMING_KEY,temporaryStyles.push([easeProp,easeVal]),node.style[easeProp]=easeVal)}timings.transitionDuration&&events.push(TRANSITIONEND_EVENT),timings.animationDuration&&events.push(ANIMATIONEND_EVENT),startTime=Date.now(),element.on(events.join(" "),onAnimationProgress),$timeout(onAnimationExpired,maxDelayTime+CLOSING_TIME_BUFFER*maxDurationTime,!1),applyAnimationToStyles(element,options)}}function onAnimationExpired(){close()}function onAnimationProgress(event){event.stopPropagation();var ev=event.originalEvent||event,timeStamp=ev.$manualTimeStamp||ev.timeStamp||Date.now(),elapsedTime=parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));Math.max(timeStamp-startTime,0)>=maxDelayTime&&elapsedTime>=maxDuration&&(animationCompleted=!0,close())}if(!animationClosed){if(!node.parentNode)return void close();var startTime,events=[],playPause=function(playAnimation){if(animationCompleted)animationPaused&&playAnimation&&(animationPaused=!1,close());else if(animationPaused=!playAnimation,timings.animationDuration){var value=blockKeyframeAnimations(node,animationPaused);animationPaused?temporaryStyles.push(value):removeFromArray(temporaryStyles,value)}},maxStagger=itemIndex>0&&(timings.transitionDuration&&0===stagger.transitionDuration||timings.animationDuration&&0===stagger.animationDuration)&&Math.max(stagger.animationDelay,stagger.transitionDelay);maxStagger?$timeout(triggerAnimationStart,Math.floor(maxStagger*itemIndex*ONE_SECOND),!1):triggerAnimationStart(),runnerHost.resume=function(){playPause(!0)},runnerHost.pause=function(){playPause(!1)}}}var node=getDomNode(element);if(!node||!node.parentNode)return closeAndReturnNoopAnimator();options=prepareAnimationOptions(options);var animationClosed,animationPaused,animationCompleted,runner,runnerHost,maxDelay,maxDelayTime,maxDuration,maxDurationTime,temporaryStyles=[],classes=element.attr("class"),styles=packageStyles(options);if(0===options.duration||!$sniffer.animations&&!$sniffer.transitions)return closeAndReturnNoopAnimator();var method=options.event&&isArray(options.event)?options.event.join(" "):options.event,isStructural=method&&options.structural,structuralClassName="",addRemoveClassName="";isStructural?structuralClassName=pendClasses(method,EVENT_CLASS_PREFIX,!0):method&&(structuralClassName=method),options.addClass&&(addRemoveClassName+=pendClasses(options.addClass,ADD_CLASS_SUFFIX)),options.removeClass&&(addRemoveClassName.length&&(addRemoveClassName+=" "),addRemoveClassName+=pendClasses(options.removeClass,REMOVE_CLASS_SUFFIX)),options.applyClassesEarly&&addRemoveClassName.length&&(applyAnimationClasses(element,options),addRemoveClassName="");var preparationClasses=[structuralClassName,addRemoveClassName].join(" ").trim(),fullClassName=classes+" "+preparationClasses,activeClasses=pendClasses(preparationClasses,ACTIVE_CLASS_SUFFIX),hasToStyles=styles.to&&Object.keys(styles.to).length>0,containsKeyframeAnimation=(options.keyframeStyle||"").length>0;if(!containsKeyframeAnimation&&!hasToStyles&&!preparationClasses)return closeAndReturnNoopAnimator();var cacheKey,stagger;if(options.stagger>0){var staggerVal=parseFloat(options.stagger);stagger={transitionDelay:staggerVal,animationDelay:staggerVal,transitionDuration:0,animationDuration:0}}else cacheKey=gcsHashFn(node,fullClassName),stagger=computeCachedCssStaggerStyles(node,preparationClasses,cacheKey,DETECT_STAGGER_CSS_PROPERTIES);options.$$skipPreparationClasses||$$jqLite.addClass(element,preparationClasses);var applyOnlyDuration;if(options.transitionStyle){var transitionStyle=[TRANSITION_PROP,options.transitionStyle];applyInlineStyle(node,transitionStyle),temporaryStyles.push(transitionStyle)}if(options.duration>=0){applyOnlyDuration=node.style[TRANSITION_PROP].length>0;var durationStyle=getCssTransitionDurationStyle(options.duration,applyOnlyDuration);applyInlineStyle(node,durationStyle),temporaryStyles.push(durationStyle)}if(options.keyframeStyle){var keyframeStyle=[ANIMATION_PROP,options.keyframeStyle];applyInlineStyle(node,keyframeStyle),temporaryStyles.push(keyframeStyle)}var itemIndex=stagger?options.staggerIndex>=0?options.staggerIndex:gcsLookup.count(cacheKey):0,isFirst=0===itemIndex;isFirst&&!options.skipBlocking&&blockTransitions(node,SAFE_FAST_FORWARD_DURATION_VALUE);var timings=computeTimings(node,fullClassName,cacheKey),relativeDelay=timings.maxDelay;maxDelay=Math.max(relativeDelay,0),maxDuration=timings.maxDuration;var flags={};return flags.hasTransitions=timings.transitionDuration>0,flags.hasAnimations=timings.animationDuration>0,flags.hasTransitionAll=flags.hasTransitions&&"all"==timings.transitionProperty,flags.applyTransitionDuration=hasToStyles&&(flags.hasTransitions&&!flags.hasTransitionAll||flags.hasAnimations&&!flags.hasTransitions),flags.applyAnimationDuration=options.duration&&flags.hasAnimations,flags.applyTransitionDelay=truthyTimingValue(options.delay)&&(flags.applyTransitionDuration||flags.hasTransitions),flags.applyAnimationDelay=truthyTimingValue(options.delay)&&flags.hasAnimations,flags.recalculateTimingStyles=addRemoveClassName.length>0,(flags.applyTransitionDuration||flags.applyAnimationDuration)&&(maxDuration=options.duration?parseFloat(options.duration):maxDuration,flags.applyTransitionDuration&&(flags.hasTransitions=!0,timings.transitionDuration=maxDuration,applyOnlyDuration=node.style[TRANSITION_PROP+PROPERTY_KEY].length>0,temporaryStyles.push(getCssTransitionDurationStyle(maxDuration,applyOnlyDuration))),flags.applyAnimationDuration&&(flags.hasAnimations=!0,timings.animationDuration=maxDuration,temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)))),0!==maxDuration||flags.recalculateTimingStyles?(null==options.duration&&timings.transitionDuration>0&&(flags.recalculateTimingStyles=flags.recalculateTimingStyles||isFirst),maxDelayTime=maxDelay*ONE_SECOND,maxDurationTime=maxDuration*ONE_SECOND,options.skipBlocking||(flags.blockTransition=timings.transitionDuration>0,flags.blockKeyframeAnimation=timings.animationDuration>0&&stagger.animationDelay>0&&0===stagger.animationDuration),applyAnimationFromStyles(element,options),flags.blockTransition||flags.blockKeyframeAnimation?applyBlocking(maxDuration):options.skipBlocking||blockTransitions(node,!1),{$$willAnimate:!0,end:endFn,start:function(){return animationClosed?void 0:(runnerHost={end:endFn,cancel:cancelFn,resume:null,pause:null},runner=new $$AnimateRunner(runnerHost),waitUntilQuiet(start),runner)}}):closeAndReturnNoopAnimator()}var cancelLastRAFRequest,applyAnimationClasses=applyAnimationClassesFactory($$jqLite),parentCounter=0,rafWaitQueue=[];return init}]}],$$AnimateCssDriverProvider=["$$animationProvider",function($$animationProvider){$$animationProvider.drivers.push("$$animateCssDriver");var NG_ANIMATE_SHIM_CLASS_NAME="ng-animate-shim",NG_ANIMATE_ANCHOR_CLASS_NAME="ng-anchor",NG_OUT_ANCHOR_CLASS_NAME="ng-anchor-out",NG_IN_ANCHOR_CLASS_NAME="ng-anchor-in";this.$get=["$animateCss","$rootScope","$$AnimateRunner","$rootElement","$$body","$sniffer","$$jqLite",function($animateCss,$rootScope,$$AnimateRunner,$rootElement,$$body,$sniffer,$$jqLite){function filterCssClasses(classes){return classes.replace(/\bng-\S+\b/g,"")}function getUniqueValues(a,b){return isString(a)&&(a=a.split(" ")),isString(b)&&(b=b.split(" ")),a.filter(function(val){return-1===b.indexOf(val)}).join(" ")}function prepareAnchoredAnimation(classes,outAnchor,inAnchor){function calculateAnchorStyles(anchor){var styles={},coords=getDomNode(anchor).getBoundingClientRect();return forEach(["width","height","top","left"],function(key){var value=coords[key];switch(key){case"top":value+=bodyNode.scrollTop;break;case"left":value+=bodyNode.scrollLeft}styles[key]=Math.floor(value)+"px"}),styles}function prepareOutAnimation(){var animator=$animateCss(clone,{addClass:NG_OUT_ANCHOR_CLASS_NAME,delay:!0,from:calculateAnchorStyles(outAnchor)});return animator.$$willAnimate?animator:null}function getClassVal(element){return element.attr("class")||""}function prepareInAnimation(){var endingClasses=filterCssClasses(getClassVal(inAnchor)),toAdd=getUniqueValues(endingClasses,startingClasses),toRemove=getUniqueValues(startingClasses,endingClasses),animator=$animateCss(clone,{to:calculateAnchorStyles(inAnchor),addClass:NG_IN_ANCHOR_CLASS_NAME+" "+toAdd,removeClass:NG_OUT_ANCHOR_CLASS_NAME+" "+toRemove,delay:!0});return animator.$$willAnimate?animator:null}function end(){clone.remove(),outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME),inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME)}var clone=jqLite(getDomNode(outAnchor).cloneNode(!0)),startingClasses=filterCssClasses(getClassVal(clone));outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME),inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME),clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME),rootBodyElement.append(clone);var animatorIn,animatorOut=prepareOutAnimation();if(!animatorOut&&(animatorIn=prepareInAnimation(),!animatorIn))return end();var startingAnimator=animatorOut||animatorIn;return{start:function(){function endFn(){currentAnimation&¤tAnimation.end()}var runner,currentAnimation=startingAnimator.start();return currentAnimation.done(function(){return currentAnimation=null,!animatorIn&&(animatorIn=prepareInAnimation())?(currentAnimation=animatorIn.start(),currentAnimation.done(function(){currentAnimation=null,end(),runner.complete()}),currentAnimation):(end(),void runner.complete())}),runner=new $$AnimateRunner({end:endFn,cancel:endFn})}}}function prepareFromToAnchorAnimation(from,to,classes,anchors){var fromAnimation=prepareRegularAnimation(from,noop),toAnimation=prepareRegularAnimation(to,noop),anchorAnimations=[];return forEach(anchors,function(anchor){var outElement=anchor.out,inElement=anchor["in"],animator=prepareAnchoredAnimation(classes,outElement,inElement);animator&&anchorAnimations.push(animator)}),fromAnimation||toAnimation||0!==anchorAnimations.length?{start:function(){function endFn(){forEach(animationRunners,function(runner){runner.end()})}var animationRunners=[];fromAnimation&&animationRunners.push(fromAnimation.start()),toAnimation&&animationRunners.push(toAnimation.start()),forEach(anchorAnimations,function(animation){animationRunners.push(animation.start())});var runner=new $$AnimateRunner({end:endFn,cancel:endFn});return $$AnimateRunner.all(animationRunners,function(status){runner.complete(status)}),runner}}:void 0}function prepareRegularAnimation(animationDetails,onBeforeClassesAppliedCb){var element=animationDetails.element,options=animationDetails.options||{};options.$$skipPreparationClasses=!0,options.skipBlocking=!0,animationDetails.structural&&(options.event=animationDetails.event,"leave"===animationDetails.event&&(options.onDone=options.domOperation)),onBeforeClassesAppliedCb(element),applyAnimationClasses(element,options),options.preparationClasses&&(options.event=concatWithSpace(options.event,options.preparationClasses));var animator=$animateCss(element,options);return animator.$$willAnimate?animator:null}if(!$sniffer.animations&&!$sniffer.transitions)return noop;var bodyNode=getDomNode($$body),rootNode=getDomNode($rootElement),rootBodyElement=jqLite(bodyNode.parentNode===rootNode?bodyNode:rootNode),applyAnimationClasses=applyAnimationClassesFactory($$jqLite);return function(animationDetails,onBeforeClassesAppliedCb){return animationDetails.from&&animationDetails.to?prepareFromToAnchorAnimation(animationDetails.from,animationDetails.to,animationDetails.classes,animationDetails.anchors):prepareRegularAnimation(animationDetails,onBeforeClassesAppliedCb)}}]}],$$AnimateJsProvider=["$animateProvider",function($animateProvider){this.$get=["$injector","$$AnimateRunner","$$rAFMutex","$$jqLite",function($injector,$$AnimateRunner,$$rAFMutex,$$jqLite){function lookupAnimations(classes){classes=isArray(classes)?classes:classes.split(" ");for(var matches=[],flagMap={},i=0;i0,b=(options.removeClass||"").length>0;return and?a&&b:a||b}var PRE_DIGEST_STATE=1,RUNNING_STATE=2,rules=this.rules={skip:[],cancel:[],join:[]};rules.join.push(function(element,newAnimation,currentAnimation){return!newAnimation.structural&&hasAnimationClasses(newAnimation.options)}),rules.skip.push(function(element,newAnimation,currentAnimation){return!newAnimation.structural&&!hasAnimationClasses(newAnimation.options)}),rules.skip.push(function(element,newAnimation,currentAnimation){return"leave"==currentAnimation.event&&newAnimation.structural}),rules.skip.push(function(element,newAnimation,currentAnimation){return currentAnimation.structural&¤tAnimation.state===RUNNING_STATE&&!newAnimation.structural}),rules.cancel.push(function(element,newAnimation,currentAnimation){ +return currentAnimation.structural&&newAnimation.structural}),rules.cancel.push(function(element,newAnimation,currentAnimation){return currentAnimation.state===RUNNING_STATE&&newAnimation.structural}),rules.cancel.push(function(element,newAnimation,currentAnimation){var nO=newAnimation.options,cO=currentAnimation.options;return nO.addClass&&nO.addClass===cO.removeClass||nO.removeClass&&nO.removeClass===cO.addClass}),this.$get=["$$rAF","$rootScope","$rootElement","$document","$$body","$$HashMap","$$animation","$$AnimateRunner","$templateRequest","$$jqLite","$$forceReflow",function($$rAF,$rootScope,$rootElement,$document,$$body,$$HashMap,$$animation,$$AnimateRunner,$templateRequest,$$jqLite,$$forceReflow){function normalizeAnimationOptions(element,options){return mergeAnimationOptions(element,options,{})}function findCallbacks(element,event){var targetNode=getDomNode(element),matches=[],entries=callbackRegistry[event];return entries&&forEach(entries,function(entry){entry.node.contains(targetNode)&&matches.push(entry.callback)}),matches}function triggerCallback(event,element,phase,data){$$rAF(function(){forEach(findCallbacks(element,event),function(callback){callback(element,phase,data)})})}function queueAnimation(element,event,options){function notifyProgress(runner,event,phase,data){triggerCallback(event,element,phase,data),runner.progress(event,phase,data)}function close(reject){clearGeneratedClasses(element,options),applyAnimationClasses(element,options),applyAnimationStyles(element,options),options.domOperation(),runner.complete(!reject)}var node,parent;element=stripCommentsFromElement(element),element&&(node=getDomNode(element),parent=element.parent()),options=prepareAnimationOptions(options);var runner=new $$AnimateRunner;if(isArray(options.addClass)&&(options.addClass=options.addClass.join(" ")),options.addClass&&!isString(options.addClass)&&(options.addClass=null),isArray(options.removeClass)&&(options.removeClass=options.removeClass.join(" ")),options.removeClass&&!isString(options.removeClass)&&(options.removeClass=null),options.from&&!isObject(options.from)&&(options.from=null),options.to&&!isObject(options.to)&&(options.to=null),!node)return close(),runner;var className=[node.className,options.addClass,options.removeClass].join(" ");if(!isAnimatableClassName(className))return close(),runner;var isStructural=["enter","move","leave"].indexOf(event)>=0,skipAnimations=!animationsEnabled||disabledElementsLookup.get(node),existingAnimation=!skipAnimations&&activeAnimationsLookup.get(node)||{},hasExistingAnimation=!!existingAnimation.state;if(skipAnimations||hasExistingAnimation&&existingAnimation.state==PRE_DIGEST_STATE||(skipAnimations=!areAnimationsAllowed(element,parent,event)),skipAnimations)return close(),runner;isStructural&&closeChildAnimations(element);var newAnimation={structural:isStructural,element:element,event:event,close:close,options:options,runner:runner};if(hasExistingAnimation){var skipAnimationFlag=isAllowed("skip",element,newAnimation,existingAnimation);if(skipAnimationFlag)return existingAnimation.state===RUNNING_STATE?(close(),runner):(mergeAnimationOptions(element,existingAnimation.options,options),existingAnimation.runner);var cancelAnimationFlag=isAllowed("cancel",element,newAnimation,existingAnimation);if(cancelAnimationFlag)if(existingAnimation.state===RUNNING_STATE)existingAnimation.runner.end();else{if(!existingAnimation.structural)return mergeAnimationOptions(element,existingAnimation.options,newAnimation.options),existingAnimation.runner;existingAnimation.close()}else{var joinAnimationFlag=isAllowed("join",element,newAnimation,existingAnimation);if(joinAnimationFlag){if(existingAnimation.state!==RUNNING_STATE)return applyGeneratedPreparationClasses(element,isStructural?event:null,options),event=newAnimation.event=existingAnimation.event,options=mergeAnimationOptions(element,existingAnimation.options,newAnimation.options),existingAnimation.runner;normalizeAnimationOptions(element,options)}}}else normalizeAnimationOptions(element,options);var isValidAnimation=newAnimation.structural;if(isValidAnimation||(isValidAnimation="animate"===newAnimation.event&&Object.keys(newAnimation.options.to||{}).length>0||hasAnimationClasses(newAnimation.options)),!isValidAnimation)return close(),clearElementAnimationState(element),runner;applyGeneratedPreparationClasses(element,isStructural?event:null,options),blockTransitions(node,SAFE_FAST_FORWARD_DURATION_VALUE);var counter=(existingAnimation.counter||0)+1;return newAnimation.counter=counter,markElementAnimationState(element,PRE_DIGEST_STATE,newAnimation),$rootScope.$$postDigest(function(){var animationDetails=activeAnimationsLookup.get(node),animationCancelled=!animationDetails;animationDetails=animationDetails||{};var parentElement=element.parent()||[],isValidAnimation=parentElement.length>0&&("animate"===animationDetails.event||animationDetails.structural||hasAnimationClasses(animationDetails.options));if(animationCancelled||animationDetails.counter!==counter||!isValidAnimation)return animationCancelled&&(applyAnimationClasses(element,options),applyAnimationStyles(element,options)),(animationCancelled||isStructural&&animationDetails.event!==event)&&(options.domOperation(),runner.end()),void(isValidAnimation||clearElementAnimationState(element));event=!animationDetails.structural&&hasAnimationClasses(animationDetails.options,!0)?"setClass":animationDetails.event,markElementAnimationState(element,RUNNING_STATE);var realRunner=$$animation(element,event,animationDetails.options,function(e){$$forceReflow(),blockTransitions(getDomNode(e),!1)});realRunner.done(function(status){close(!status);var animationDetails=activeAnimationsLookup.get(node);animationDetails&&animationDetails.counter===counter&&clearElementAnimationState(getDomNode(element)),notifyProgress(runner,event,"close",{})}),runner.setHost(realRunner),notifyProgress(runner,event,"start",{})}),runner}function closeChildAnimations(element){var node=getDomNode(element),children=node.querySelectorAll("["+NG_ANIMATE_ATTR_NAME+"]");forEach(children,function(child){var state=parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME)),animationDetails=activeAnimationsLookup.get(child);switch(state){case RUNNING_STATE:animationDetails.runner.end();case PRE_DIGEST_STATE:animationDetails&&activeAnimationsLookup.remove(child)}})}function clearElementAnimationState(element){var node=getDomNode(element);node.removeAttribute(NG_ANIMATE_ATTR_NAME),activeAnimationsLookup.remove(node)}function isMatchingElement(nodeOrElmA,nodeOrElmB){return getDomNode(nodeOrElmA)===getDomNode(nodeOrElmB)}function areAnimationsAllowed(element,parentElement,event){var animateChildren,bodyElementDetected=isMatchingElement(element,$$body)||"HTML"===element[0].nodeName,rootElementDetected=isMatchingElement(element,$rootElement),parentAnimationDetected=!1,parentHost=element.data(NG_ANIMATE_PIN_DATA);for(parentHost&&(parentElement=parentHost);parentElement&&parentElement.length;){rootElementDetected||(rootElementDetected=isMatchingElement(parentElement,$rootElement));var parentNode=parentElement[0];if(parentNode.nodeType!==ELEMENT_NODE)break;var details=activeAnimationsLookup.get(parentNode)||{};if(parentAnimationDetected||(parentAnimationDetected=details.structural||disabledElementsLookup.get(parentNode)),isUndefined(animateChildren)||animateChildren===!0){var value=parentElement.data(NG_ANIMATE_CHILDREN_DATA);isDefined(value)&&(animateChildren=value)}if(parentAnimationDetected&&animateChildren===!1)break;rootElementDetected||(rootElementDetected=isMatchingElement(parentElement,$rootElement),rootElementDetected||(parentHost=parentElement.data(NG_ANIMATE_PIN_DATA),parentHost&&(parentElement=parentHost))),bodyElementDetected||(bodyElementDetected=isMatchingElement(parentElement,$$body)),parentElement=parentElement.parent()}var allowAnimation=!parentAnimationDetected||animateChildren;return allowAnimation&&rootElementDetected&&bodyElementDetected}function markElementAnimationState(element,state,details){details=details||{},details.state=state;var node=getDomNode(element);node.setAttribute(NG_ANIMATE_ATTR_NAME,state);var oldValue=activeAnimationsLookup.get(node),newValue=oldValue?extend(oldValue,details):details;activeAnimationsLookup.put(node,newValue)}var activeAnimationsLookup=new $$HashMap,disabledElementsLookup=new $$HashMap,animationsEnabled=null,deregisterWatch=$rootScope.$watch(function(){return 0===$templateRequest.totalPendingRequests},function(isEmpty){isEmpty&&(deregisterWatch(),$rootScope.$$postDigest(function(){$rootScope.$$postDigest(function(){null===animationsEnabled&&(animationsEnabled=!0)})}))}),callbackRegistry={},classNameFilter=$animateProvider.classNameFilter(),isAnimatableClassName=classNameFilter?function(className){return classNameFilter.test(className)}:function(){return!0},applyAnimationClasses=applyAnimationClassesFactory($$jqLite);return{on:function(event,container,callback){var node=extractElementNode(container);callbackRegistry[event]=callbackRegistry[event]||[],callbackRegistry[event].push({node:node,callback:callback})},off:function(event,container,callback){function filterFromRegistry(list,matchContainer,matchCallback){var containerNode=extractElementNode(matchContainer);return list.filter(function(entry){var isMatch=entry.node===containerNode&&(!matchCallback||entry.callback===matchCallback);return!isMatch})}var entries=callbackRegistry[event];entries&&(callbackRegistry[event]=1===arguments.length?null:filterFromRegistry(entries,container,callback))},pin:function(element,parentElement){assertArg(isElement(element),"element","not an element"),assertArg(isElement(parentElement),"parentElement","not an element"),element.data(NG_ANIMATE_PIN_DATA,parentElement)},push:function(element,event,options,domOperation){return options=options||{},options.domOperation=domOperation,queueAnimation(element,event,options)},enabled:function(element,bool){var argCount=arguments.length;if(0===argCount)bool=!!animationsEnabled;else{var hasElement=isElement(element);if(hasElement){var node=getDomNode(element),recordExists=disabledElementsLookup.get(node);1===argCount?bool=!recordExists:(bool=!!bool,bool?recordExists&&disabledElementsLookup.remove(node):disabledElementsLookup.put(node,!0))}else bool=animationsEnabled=!!element}return bool}}}]}],$$rAFMutexFactory=["$$rAF",function($$rAF){return function(){var passed=!1;return $$rAF(function(){passed=!0}),function(fn){passed?fn():$$rAF(fn)}}}],$$AnimateRunnerFactory=["$q","$$rAFMutex",function($q,$$rAFMutex){function AnimateRunner(host){this.setHost(host),this._doneCallbacks=[],this._runInAnimationFrame=$$rAFMutex(),this._state=0}var INITIAL_STATE=0,DONE_PENDING_STATE=1,DONE_COMPLETE_STATE=2;return AnimateRunner.chain=function(chain,callback){function next(){return index===chain.length?void callback(!0):void chain[index](function(response){return response===!1?void callback(!1):(index++,void next())})}var index=0;next()},AnimateRunner.all=function(runners,callback){function onProgress(response){status=status&&response,++count===runners.length&&callback(status)}var count=0,status=!0;forEach(runners,function(runner){runner.done(onProgress)})},AnimateRunner.prototype={setHost:function(host){this.host=host||{}},done:function(fn){this._state===DONE_COMPLETE_STATE?fn():this._doneCallbacks.push(fn)},progress:noop,getPromise:function(){if(!this.promise){var self=this;this.promise=$q(function(resolve,reject){self.done(function(status){status===!1?reject():resolve()})})}return this.promise},then:function(resolveHandler,rejectHandler){return this.getPromise().then(resolveHandler,rejectHandler)},"catch":function(handler){return this.getPromise()["catch"](handler)},"finally":function(handler){return this.getPromise()["finally"](handler)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end(),this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel(),this._resolve(!1)},complete:function(response){var self=this;self._state===INITIAL_STATE&&(self._state=DONE_PENDING_STATE,self._runInAnimationFrame(function(){self._resolve(response)}))},_resolve:function(response){this._state!==DONE_COMPLETE_STATE&&(forEach(this._doneCallbacks,function(fn){fn(response)}),this._doneCallbacks.length=0,this._state=DONE_COMPLETE_STATE)}},AnimateRunner}],$$AnimationProvider=["$animateProvider",function($animateProvider){function setRunner(element,runner){element.data(RUNNER_STORAGE_KEY,runner)}function removeRunner(element){element.removeData(RUNNER_STORAGE_KEY)}function getRunner(element){return element.data(RUNNER_STORAGE_KEY)}var NG_ANIMATE_REF_ATTR="ng-animate-ref",drivers=this.drivers=[],RUNNER_STORAGE_KEY="$$animationRunner";this.$get=["$$jqLite","$rootScope","$injector","$$AnimateRunner","$$HashMap",function($$jqLite,$rootScope,$injector,$$AnimateRunner,$$HashMap){function sortAnimations(animations){function processNode(entry){if(entry.processed)return entry;entry.processed=!0;var elementNode=entry.domNode,parentNode=elementNode.parentNode;lookup.put(elementNode,entry);for(var parentEntry;parentNode;){if(parentEntry=lookup.get(parentNode)){parentEntry.processed||(parentEntry=processNode(parentEntry));break}parentNode=parentNode.parentNode}return(parentEntry||tree).children.push(entry),entry}function flatten(tree){var i,result=[],queue=[];for(i=0;i=remainingLevelEntries&&(remainingLevelEntries=nextLevelEntries,nextLevelEntries=0,result=result.concat(row),row=[]),row.push(entry.fn),forEach(entry.children,function(childEntry){nextLevelEntries++,queue.push(childEntry)}),remainingLevelEntries--}return row.length&&(result=result.concat(row)),result}var i,tree={children:[]},lookup=new $$HashMap;for(i=0;i=0,anchorNodes=animation.structural?getAnchorNodes(node):[];if(anchorNodes.length){var direction=enterOrMove?"to":"from";forEach(anchorNodes,function(anchor){var key=anchor.getAttribute(NG_ANIMATE_REF_ATTR);refLookup[key]=refLookup[key]||{},refLookup[key][direction]={animationID:index,element:jqLite(anchor)}})}else preparedAnimations.push(animation)});var usedIndicesLookup={},anchorGroups={};return forEach(refLookup,function(operations,key){var from=operations.from,to=operations.to;if(!from||!to){var index=from?from.animationID:to.animationID,indexKey=index.toString();return void(usedIndicesLookup[indexKey]||(usedIndicesLookup[indexKey]=!0,preparedAnimations.push(animations[index])))}var fromAnimation=animations[from.animationID],toAnimation=animations[to.animationID],lookupKey=from.animationID.toString();if(!anchorGroups[lookupKey]){var group=anchorGroups[lookupKey]={beforeStart:function(){fromAnimation.beforeStart(),toAnimation.beforeStart()},close:function(){fromAnimation.close(),toAnimation.close()},classes:cssClassesIntersection(fromAnimation.classes,toAnimation.classes),from:fromAnimation,to:toAnimation,anchors:[]};group.classes.length?preparedAnimations.push(group):(preparedAnimations.push(fromAnimation),preparedAnimations.push(toAnimation))}anchorGroups[lookupKey].anchors.push({out:from.element,"in":to.element})}),preparedAnimations}function cssClassesIntersection(a,b){a=a.split(" "),b=b.split(" ");for(var matches=[],i=0;i=0;i--){var driverName=drivers[i];if($injector.has(driverName)){var factory=$injector.get(driverName),driver=factory(animationDetails,onBeforeClassesAppliedCb);if(driver)return driver}}}function beforeStart(){element.addClass(NG_ANIMATE_CLASSNAME),tempClasses&&$$jqLite.addClass(element,tempClasses)}function updateAnimationRunners(animation,newRunner){function update(element){getRunner(element).setHost(newRunner)}animation.from&&animation.to?(update(animation.from.element),update(animation.to.element)):update(animation.element)}function handleDestroyedElement(){var runner=getRunner(element);!runner||"leave"===event&&options.$$domOperationFired||runner.end()}function close(rejected){element.off("$destroy",handleDestroyedElement),removeRunner(element),applyAnimationClasses(element,options),applyAnimationStyles(element,options),options.domOperation(),tempClasses&&$$jqLite.removeClass(element,tempClasses),element.removeClass(NG_ANIMATE_CLASSNAME),runner.complete(!rejected)}options=prepareAnimationOptions(options);var isStructural=["enter","move","leave"].indexOf(event)>=0,runner=new $$AnimateRunner({end:function(){close()},cancel:function(){close(!0)}});if(!drivers.length)return close(),runner;setRunner(element,runner);var classes=mergeClasses(element.attr("class"),mergeClasses(options.addClass,options.removeClass)),tempClasses=options.tempClasses;return tempClasses&&(classes+=" "+tempClasses,options.tempClasses=null),animationQueue.push({element:element,classes:classes,event:event,structural:isStructural,options:options,beforeStart:beforeStart,close:close}),element.on("$destroy",handleDestroyedElement),animationQueue.length>1?runner:($rootScope.$$postDigest(function(){var animations=[];forEach(animationQueue,function(entry){var elm=entry.element;getRunner(elm)&&getDomNode(elm).parentNode?animations.push(entry):entry.close()}),animationQueue.length=0;var groupedAnimations=groupAnimations(animations),toBeSortedAnimations=[];forEach(groupedAnimations,function(animationEntry){toBeSortedAnimations.push({domNode:getDomNode(animationEntry.from?animationEntry.from.element:animationEntry.element),fn:function(){animationEntry.beforeStart();var startAnimationFn,closeFn=animationEntry.close,targetElement=animationEntry.anchors?animationEntry.from.element||animationEntry.to.element:animationEntry.element;if(getRunner(targetElement)){var operation=invokeFirstDriver(animationEntry,onBeforeClassesAppliedCb);operation&&(startAnimationFn=operation.start)}if(startAnimationFn){var animationRunner=startAnimationFn();animationRunner.done(function(status){closeFn(!status)}),updateAnimationRunners(animationEntry,animationRunner)}else closeFn()}})}),forEach(sortAnimations(toBeSortedAnimations),function(triggerAnimation){triggerAnimation()})}),runner)}}]}];angular.module("ngAnimate",[]).provider("$$body",$$BodyProvider).directive("ngAnimateChildren",$$AnimateChildrenDirective).factory("$$rAFMutex",$$rAFMutexFactory).factory("$$AnimateRunner",$$AnimateRunnerFactory).provider("$$animateQueue",$$AnimateQueueProvider).provider("$$animation",$$AnimationProvider).provider("$animateCss",$AnimateCssProvider).provider("$$animateCssDriver",$$AnimateCssDriverProvider).provider("$$animateJs",$$AnimateJsProvider).provider("$$animateJsDriver",$$AnimateJsDriverProvider)}(window,window.angular); + +},{}],11:[function(require,module,exports){ +require("./angular-animate"),module.exports="ngAnimate"; + +},{"./angular-animate":10}],12:[function(require,module,exports){ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function($q,$timeout,$rootScope){function findEndEventName(endEventNames){for(var name in endEventNames)if(void 0!==transElement.style[name])return endEventNames[name]}var $transition=function(element,trigger,options){options=options||{};var deferred=$q.defer(),endEventName=$transition[options.animation?"animationEndEventName":"transitionEndEventName"],transitionEndHandler=function(event){$rootScope.$apply(function(){element.unbind(endEventName,transitionEndHandler),deferred.resolve(element)})};return endEventName&&element.bind(endEventName,transitionEndHandler),$timeout(function(){angular.isString(trigger)?element.addClass(trigger):angular.isFunction(trigger)?trigger(element):angular.isObject(trigger)&&element.css(trigger),endEventName||deferred.resolve(element)}),deferred.promise.cancel=function(){endEventName&&element.unbind(endEventName,transitionEndHandler),deferred.reject("Transition cancelled")},deferred.promise},transElement=document.createElement("trans"),transitionEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},animationEndEventNames={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return $transition.transitionEndEventName=findEndEventName(transitionEndEventNames),$transition.animationEndEventName=findEndEventName(animationEndEventNames),$transition}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function($transition){return{link:function(scope,element,attrs){function doTransition(change){function newTransitionDone(){currentTransition===newTransition&&(currentTransition=void 0)}var newTransition=$transition(element,change);return currentTransition&¤tTransition.cancel(),currentTransition=newTransition,newTransition.then(newTransitionDone,newTransitionDone),newTransition}function expand(){initialAnimSkip?(initialAnimSkip=!1,expandDone()):(element.removeClass("collapse").addClass("collapsing"),doTransition({height:element[0].scrollHeight+"px"}).then(expandDone))}function expandDone(){element.removeClass("collapsing"),element.addClass("collapse in"),element.css({height:"auto"})}function collapse(){if(initialAnimSkip)initialAnimSkip=!1,collapseDone(),element.css({height:0});else{element.css({height:element[0].scrollHeight+"px"});element[0].offsetWidth;element.removeClass("collapse in").addClass("collapsing"),doTransition({height:0}).then(collapseDone)}}function collapseDone(){element.removeClass("collapsing"),element.addClass("collapse")}var currentTransition,initialAnimSkip=!0;scope.$watch(attrs.collapse,function(shouldCollapse){shouldCollapse?collapse():expand()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function($scope,$attrs,accordionConfig){this.groups=[],this.closeOthers=function(openGroup){var closeOthers=angular.isDefined($attrs.closeOthers)?$scope.$eval($attrs.closeOthers):accordionConfig.closeOthers;closeOthers&&angular.forEach(this.groups,function(group){group!==openGroup&&(group.isOpen=!1)})},this.addGroup=function(groupScope){var that=this;this.groups.push(groupScope),groupScope.$on("$destroy",function(event){that.removeGroup(groupScope)})},this.removeGroup=function(group){var index=this.groups.indexOf(group);-1!==index&&this.groups.splice(index,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(element){this.heading=element}},link:function(scope,element,attrs,accordionCtrl){accordionCtrl.addGroup(scope),scope.$watch("isOpen",function(value){value&&accordionCtrl.closeOthers(scope)}),scope.toggleOpen=function(){scope.isDisabled||(scope.isOpen=!scope.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(scope,element,attr,accordionGroupCtrl,transclude){accordionGroupCtrl.setHeading(transclude(scope,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(scope,element,attr,controller){scope.$watch(function(){return controller[attr.accordionTransclude]},function(heading){heading&&(element.html(""),element.append(heading))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function($scope,$attrs){$scope.closeable="close"in $attrs,this.close=$scope.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}).directive("dismissOnTimeout",["$timeout",function($timeout){return{require:"alert",link:function(scope,element,attrs,alertCtrl){$timeout(function(){alertCtrl.close()},parseInt(attrs.dismissOnTimeout,10))}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(scope,element,attr){element.addClass("ng-binding").data("$binding",attr.bindHtmlUnsafe),scope.$watch(attr.bindHtmlUnsafe,function(value){element.html(value||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(buttonConfig){this.activeClass=buttonConfig.activeClass||"active",this.toggleEvent=buttonConfig.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(scope,element,attrs,ctrls){var buttonsCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl.$render=function(){element.toggleClass(buttonsCtrl.activeClass,angular.equals(ngModelCtrl.$modelValue,scope.$eval(attrs.btnRadio)))},element.bind(buttonsCtrl.toggleEvent,function(){var isActive=element.hasClass(buttonsCtrl.activeClass);(!isActive||angular.isDefined(attrs.uncheckable))&&scope.$apply(function(){ngModelCtrl.$setViewValue(isActive?null:scope.$eval(attrs.btnRadio)),ngModelCtrl.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(scope,element,attrs,ctrls){function getTrueValue(){return getCheckboxValue(attrs.btnCheckboxTrue,!0)}function getFalseValue(){return getCheckboxValue(attrs.btnCheckboxFalse,!1)}function getCheckboxValue(attributeValue,defaultValue){var val=scope.$eval(attributeValue);return angular.isDefined(val)?val:defaultValue}var buttonsCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl.$render=function(){element.toggleClass(buttonsCtrl.activeClass,angular.equals(ngModelCtrl.$modelValue,getTrueValue()))},element.bind(buttonsCtrl.toggleEvent,function(){scope.$apply(function(){ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass)?getFalseValue():getTrueValue()),ngModelCtrl.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$interval","$transition",function($scope,$timeout,$interval,$transition){function restartTimer(){resetTimer();var interval=+$scope.interval;!isNaN(interval)&&interval>0&&(currentInterval=$interval(timerFn,interval))}function resetTimer(){currentInterval&&($interval.cancel(currentInterval),currentInterval=null)}function timerFn(){var interval=+$scope.interval;isPlaying&&!isNaN(interval)&&interval>0?$scope.next():$scope.pause()}var currentInterval,isPlaying,self=this,slides=self.slides=$scope.slides=[],currentIndex=-1;self.currentSlide=null;var destroyed=!1;self.select=$scope.select=function(nextSlide,direction){function goNext(){if(!destroyed){if(self.currentSlide&&angular.isString(direction)&&!$scope.noTransition&&nextSlide.$element){nextSlide.$element.addClass(direction);nextSlide.$element[0].offsetWidth;angular.forEach(slides,function(slide){angular.extend(slide,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(nextSlide,{direction:direction,active:!0,entering:!0}),angular.extend(self.currentSlide||{},{direction:direction,leaving:!0}),$scope.$currentTransition=$transition(nextSlide.$element,{}),function(next,current){$scope.$currentTransition.then(function(){transitionDone(next,current)},function(){transitionDone(next,current)})}(nextSlide,self.currentSlide)}else transitionDone(nextSlide,self.currentSlide);self.currentSlide=nextSlide,currentIndex=nextIndex,restartTimer()}}function transitionDone(next,current){angular.extend(next,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(current||{},{direction:"",active:!1,leaving:!1,entering:!1}),$scope.$currentTransition=null}var nextIndex=slides.indexOf(nextSlide);void 0===direction&&(direction=nextIndex>currentIndex?"next":"prev"),nextSlide&&nextSlide!==self.currentSlide&&($scope.$currentTransition?($scope.$currentTransition.cancel(),$timeout(goNext)):goNext())},$scope.$on("$destroy",function(){destroyed=!0}),self.indexOfSlide=function(slide){return slides.indexOf(slide)},$scope.next=function(){var newIndex=(currentIndex+1)%slides.length;return $scope.$currentTransition?void 0:self.select(slides[newIndex],"next")},$scope.prev=function(){var newIndex=0>currentIndex-1?slides.length-1:currentIndex-1;return $scope.$currentTransition?void 0:self.select(slides[newIndex],"prev")},$scope.isActive=function(slide){return self.currentSlide===slide},$scope.$watch("interval",restartTimer),$scope.$on("$destroy",resetTimer),$scope.play=function(){isPlaying||(isPlaying=!0,restartTimer())},$scope.pause=function(){$scope.noPause||(isPlaying=!1,resetTimer())},self.addSlide=function(slide,element){slide.$element=element,slides.push(slide),1===slides.length||slide.active?(self.select(slides[slides.length-1]),1==slides.length&&$scope.play()):slide.active=!1},self.removeSlide=function(slide){var index=slides.indexOf(slide);slides.splice(index,1),slides.length>0&&slide.active?index>=slides.length?self.select(slides[index-1]):self.select(slides[index]):currentIndex>index&¤tIndex--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(scope,element,attrs,carouselCtrl){carouselCtrl.addSlide(scope,element),scope.$on("$destroy",function(){carouselCtrl.removeSlide(scope)}),scope.$watch("active",function(active){active&&carouselCtrl.select(scope)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function($locale,orderByFilter){function createParser(format){var map=[],regex=format.split("");return angular.forEach(formatCodeToRegex,function(data,code){var index=format.indexOf(code);if(index>-1){format=format.split(""),regex[index]="("+data.regex+")",format[index]="$";for(var i=index+1,n=index+code.length;n>i;i++)regex[i]="",format[i]="$";format=format.join(""),map.push({index:index,apply:data.apply})}}),{regex:new RegExp("^"+regex.join("")+"$"),map:orderByFilter(map,"index")}}function isValid(year,month,date){return 1===month&&date>28?29===date&&(year%4===0&&year%100!==0||year%400===0):3===month||5===month||8===month||10===month?31>date:!0}this.parsers={};var formatCodeToRegex={yyyy:{regex:"\\d{4}",apply:function(value){this.year=+value}},yy:{regex:"\\d{2}",apply:function(value){this.year=+value+2e3}},y:{regex:"\\d{1,4}",apply:function(value){this.year=+value}},MMMM:{regex:$locale.DATETIME_FORMATS.MONTH.join("|"),apply:function(value){this.month=$locale.DATETIME_FORMATS.MONTH.indexOf(value)}},MMM:{regex:$locale.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(value){this.month=$locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(value){this.month=value-1}},M:{regex:"[1-9]|1[0-2]",apply:function(value){this.month=value-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(value){this.date=+value}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(value){this.date=+value}},EEEE:{regex:$locale.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:$locale.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(input,format){if(!angular.isString(input)||!format)return input;format=$locale.DATETIME_FORMATS[format]||format,this.parsers[format]||(this.parsers[format]=createParser(format));var parser=this.parsers[format],regex=parser.regex,map=parser.map,results=input.match(regex);if(results&&results.length){for(var dt,fields={year:1900,month:0,date:1,hours:0},i=1,n=results.length;n>i;i++){var mapper=map[i-1];mapper.apply&&mapper.apply.call(fields,results[i])}return isValid(fields.year,fields.month,fields.date)&&(dt=new Date(fields.year,fields.month,fields.date,fields.hours)),dt}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function($document,$window){function getStyle(el,cssprop){return el.currentStyle?el.currentStyle[cssprop]:$window.getComputedStyle?$window.getComputedStyle(el)[cssprop]:el.style[cssprop]}function isStaticPositioned(element){return"static"===(getStyle(element,"position")||"static")}var parentOffsetEl=function(element){for(var docDomEl=$document[0],offsetParent=element.offsetParent||docDomEl;offsetParent&&offsetParent!==docDomEl&&isStaticPositioned(offsetParent);)offsetParent=offsetParent.offsetParent;return offsetParent||docDomEl};return{position:function(element){var elBCR=this.offset(element),offsetParentBCR={top:0,left:0},offsetParentEl=parentOffsetEl(element[0]);offsetParentEl!=$document[0]&&(offsetParentBCR=this.offset(angular.element(offsetParentEl)),offsetParentBCR.top+=offsetParentEl.clientTop-offsetParentEl.scrollTop,offsetParentBCR.left+=offsetParentEl.clientLeft-offsetParentEl.scrollLeft);var boundingClientRect=element[0].getBoundingClientRect();return{width:boundingClientRect.width||element.prop("offsetWidth"),height:boundingClientRect.height||element.prop("offsetHeight"),top:elBCR.top-offsetParentBCR.top,left:elBCR.left-offsetParentBCR.left}},offset:function(element){var boundingClientRect=element[0].getBoundingClientRect();return{width:boundingClientRect.width||element.prop("offsetWidth"),height:boundingClientRect.height||element.prop("offsetHeight"),top:boundingClientRect.top+($window.pageYOffset||$document[0].documentElement.scrollTop),left:boundingClientRect.left+($window.pageXOffset||$document[0].documentElement.scrollLeft)}},positionElements:function(hostEl,targetEl,positionStr,appendToBody){var hostElPos,targetElWidth,targetElHeight,targetElPos,positionStrParts=positionStr.split("-"),pos0=positionStrParts[0],pos1=positionStrParts[1]||"center";hostElPos=appendToBody?this.offset(hostEl):this.position(hostEl),targetElWidth=targetEl.prop("offsetWidth"),targetElHeight=targetEl.prop("offsetHeight");var shiftWidth={center:function(){return hostElPos.left+hostElPos.width/2-targetElWidth/2},left:function(){return hostElPos.left},right:function(){return hostElPos.left+hostElPos.width}},shiftHeight={center:function(){return hostElPos.top+hostElPos.height/2-targetElHeight/2},top:function(){return hostElPos.top},bottom:function(){return hostElPos.top+hostElPos.height}};switch(pos0){case"right":targetElPos={top:shiftHeight[pos1](),left:shiftWidth[pos0]()};break;case"left":targetElPos={top:shiftHeight[pos1](),left:hostElPos.left-targetElWidth};break;case"bottom":targetElPos={top:shiftHeight[pos0](),left:shiftWidth[pos1]()};break;default:targetElPos={top:hostElPos.top-targetElHeight,left:shiftWidth[pos1]()}}return targetElPos}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function($scope,$attrs,$parse,$interpolate,$timeout,$log,dateFilter,datepickerConfig){var self=this,ngModelCtrl={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(key,index){self[key]=angular.isDefined($attrs[key])?8>index?$interpolate($attrs[key])($scope.$parent):$scope.$parent.$eval($attrs[key]):datepickerConfig[key]}),angular.forEach(["minDate","maxDate"],function(key){$attrs[key]?$scope.$parent.$watch($parse($attrs[key]),function(value){self[key]=value?new Date(value):null,self.refreshView()}):self[key]=datepickerConfig[key]?new Date(datepickerConfig[key]):null}),$scope.datepickerMode=$scope.datepickerMode||datepickerConfig.datepickerMode,$scope.uniqueId="datepicker-"+$scope.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined($attrs.initDate)?$scope.$parent.$eval($attrs.initDate):new Date,$scope.isActive=function(dateObject){return 0===self.compare(dateObject.date,self.activeDate)?($scope.activeDateId=dateObject.uid,!0):!1},this.init=function(ngModelCtrl_){ngModelCtrl=ngModelCtrl_,ngModelCtrl.$render=function(){self.render()}},this.render=function(){if(ngModelCtrl.$modelValue){var date=new Date(ngModelCtrl.$modelValue),isValid=!isNaN(date);isValid?this.activeDate=date:$log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),ngModelCtrl.$setValidity("date",isValid)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var date=ngModelCtrl.$modelValue?new Date(ngModelCtrl.$modelValue):null;ngModelCtrl.$setValidity("date-disabled",!date||this.element&&!this.isDisabled(date))}},this.createDateObject=function(date,format){var model=ngModelCtrl.$modelValue?new Date(ngModelCtrl.$modelValue):null;return{date:date,label:dateFilter(date,format),selected:model&&0===this.compare(date,model),disabled:this.isDisabled(date),current:0===this.compare(date,new Date)}},this.isDisabled=function(date){return this.minDate&&this.compare(date,this.minDate)<0||this.maxDate&&this.compare(date,this.maxDate)>0||$attrs.dateDisabled&&$scope.dateDisabled({date:date,mode:$scope.datepickerMode})},this.split=function(arr,size){for(var arrays=[];arr.length>0;)arrays.push(arr.splice(0,size));return arrays},$scope.select=function(date){if($scope.datepickerMode===self.minMode){var dt=ngModelCtrl.$modelValue?new Date(ngModelCtrl.$modelValue):new Date(0,0,0,0,0,0,0);dt.setFullYear(date.getFullYear(),date.getMonth(),date.getDate()),ngModelCtrl.$setViewValue(dt),ngModelCtrl.$render()}else self.activeDate=date,$scope.datepickerMode=self.modes[self.modes.indexOf($scope.datepickerMode)-1]},$scope.move=function(direction){var year=self.activeDate.getFullYear()+direction*(self.step.years||0),month=self.activeDate.getMonth()+direction*(self.step.months||0);self.activeDate.setFullYear(year,month,1),self.refreshView()},$scope.toggleMode=function(direction){direction=direction||1,$scope.datepickerMode===self.maxMode&&1===direction||$scope.datepickerMode===self.minMode&&-1===direction||($scope.datepickerMode=self.modes[self.modes.indexOf($scope.datepickerMode)+direction])},$scope.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var focusElement=function(){$timeout(function(){self.element[0].focus()},0,!1)};$scope.$on("datepicker.focus",focusElement),$scope.keydown=function(evt){var key=$scope.keys[evt.which];if(key&&!evt.shiftKey&&!evt.altKey)if(evt.preventDefault(),evt.stopPropagation(),"enter"===key||"space"===key){if(self.isDisabled(self.activeDate))return;$scope.select(self.activeDate),focusElement()}else!evt.ctrlKey||"up"!==key&&"down"!==key?(self.handleKeyDown(key,evt),self.refreshView()):($scope.toggleMode("up"===key?1:-1),focusElement())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(scope,element,attrs,ctrls){var datepickerCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl&&datepickerCtrl.init(ngModelCtrl)}}}).directive("daypicker",["dateFilter",function(dateFilter){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(scope,element,attrs,ctrl){function getDaysInMonth(year,month){return 1!==month||year%4!==0||year%100===0&&year%400!==0?DAYS_IN_MONTH[month]:29}function getDates(startDate,n){var dates=new Array(n),current=new Date(startDate),i=0;for(current.setHours(12);n>i;)dates[i++]=new Date(current),current.setDate(current.getDate()+1);return dates}function getISO8601WeekNumber(date){var checkDate=new Date(date);checkDate.setDate(checkDate.getDate()+4-(checkDate.getDay()||7));var time=checkDate.getTime();return checkDate.setMonth(0),checkDate.setDate(1),Math.floor(Math.round((time-checkDate)/864e5)/7)+1}scope.showWeeks=ctrl.showWeeks,ctrl.step={months:1},ctrl.element=element;var DAYS_IN_MONTH=[31,28,31,30,31,30,31,31,30,31,30,31];ctrl._refreshView=function(){var year=ctrl.activeDate.getFullYear(),month=ctrl.activeDate.getMonth(),firstDayOfMonth=new Date(year,month,1),difference=ctrl.startingDay-firstDayOfMonth.getDay(),numDisplayedFromPreviousMonth=difference>0?7-difference:-difference,firstDate=new Date(firstDayOfMonth);numDisplayedFromPreviousMonth>0&&firstDate.setDate(-numDisplayedFromPreviousMonth+1);for(var days=getDates(firstDate,42),i=0;42>i;i++)days[i]=angular.extend(ctrl.createDateObject(days[i],ctrl.formatDay),{secondary:days[i].getMonth()!==month,uid:scope.uniqueId+"-"+i});scope.labels=new Array(7);for(var j=0;7>j;j++)scope.labels[j]={abbr:dateFilter(days[j].date,ctrl.formatDayHeader),full:dateFilter(days[j].date,"EEEE")};if(scope.title=dateFilter(ctrl.activeDate,ctrl.formatDayTitle),scope.rows=ctrl.split(days,7),scope.showWeeks){scope.weekNumbers=[];for(var weekNumber=getISO8601WeekNumber(scope.rows[0][0].date),numWeeks=scope.rows.length;scope.weekNumbers.push(weekNumber++)i;i++)months[i]=angular.extend(ctrl.createDateObject(new Date(year,i,1),ctrl.formatMonth),{uid:scope.uniqueId+"-"+i});scope.title=dateFilter(ctrl.activeDate,ctrl.formatMonthTitle),scope.rows=ctrl.split(months,3)},ctrl.compare=function(date1,date2){return new Date(date1.getFullYear(),date1.getMonth())-new Date(date2.getFullYear(),date2.getMonth())},ctrl.handleKeyDown=function(key,evt){var date=ctrl.activeDate.getMonth();if("left"===key)date-=1;else if("up"===key)date-=3;else if("right"===key)date+=1;else if("down"===key)date+=3;else if("pageup"===key||"pagedown"===key){var year=ctrl.activeDate.getFullYear()+("pageup"===key?-1:1);ctrl.activeDate.setFullYear(year)}else"home"===key?date=0:"end"===key&&(date=11);ctrl.activeDate.setMonth(date)},ctrl.refreshView()}}}]).directive("yearpicker",["dateFilter",function(dateFilter){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(scope,element,attrs,ctrl){function getStartingYear(year){return parseInt((year-1)/range,10)*range+1}var range=ctrl.yearRange;ctrl.step={years:range},ctrl.element=element,ctrl._refreshView=function(){for(var years=new Array(range),i=0,start=getStartingYear(ctrl.activeDate.getFullYear());range>i;i++)years[i]=angular.extend(ctrl.createDateObject(new Date(start+i,0,1),ctrl.formatYear),{uid:scope.uniqueId+"-"+i});scope.title=[years[0].label,years[range-1].label].join(" - "),scope.rows=ctrl.split(years,5)},ctrl.compare=function(date1,date2){return date1.getFullYear()-date2.getFullYear()},ctrl.handleKeyDown=function(key,evt){var date=ctrl.activeDate.getFullYear();"left"===key?date-=1:"up"===key?date-=5:"right"===key?date+=1:"down"===key?date+=5:"pageup"===key||"pagedown"===key?date+=("pageup"===key?-1:1)*ctrl.step.years:"home"===key?date=getStartingYear(ctrl.activeDate.getFullYear()):"end"===key&&(date=getStartingYear(ctrl.activeDate.getFullYear())+range-1),ctrl.activeDate.setFullYear(date)},ctrl.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function($compile,$parse,$document,$position,dateFilter,dateParser,datepickerPopupConfig){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(scope,element,attrs,ngModel){function cameltoDash(string){return string.replace(/([A-Z])/g,function($1){return"-"+$1.toLowerCase()})}function parseDate(viewValue){if(viewValue){if(angular.isDate(viewValue)&&!isNaN(viewValue))return ngModel.$setValidity("date",!0),viewValue;if(angular.isString(viewValue)){var date=dateParser.parse(viewValue,dateFormat)||new Date(viewValue);return isNaN(date)?void ngModel.$setValidity("date",!1):(ngModel.$setValidity("date",!0),date)}return void ngModel.$setValidity("date",!1)}return ngModel.$setValidity("date",!0),null}var dateFormat,closeOnDateSelection=angular.isDefined(attrs.closeOnDateSelection)?scope.$parent.$eval(attrs.closeOnDateSelection):datepickerPopupConfig.closeOnDateSelection,appendToBody=angular.isDefined(attrs.datepickerAppendToBody)?scope.$parent.$eval(attrs.datepickerAppendToBody):datepickerPopupConfig.appendToBody;scope.showButtonBar=angular.isDefined(attrs.showButtonBar)?scope.$parent.$eval(attrs.showButtonBar):datepickerPopupConfig.showButtonBar,scope.getText=function(key){return scope[key+"Text"]||datepickerPopupConfig[key+"Text"]},attrs.$observe("datepickerPopup",function(value){dateFormat=value||datepickerPopupConfig.datepickerPopup,ngModel.$render()});var popupEl=angular.element("

");popupEl.attr({"ng-model":"date","ng-change":"dateSelection()"});var datepickerEl=angular.element(popupEl.children()[0]);attrs.datepickerOptions&&angular.forEach(scope.$parent.$eval(attrs.datepickerOptions),function(value,option){datepickerEl.attr(cameltoDash(option),value)}),scope.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(key){if(attrs[key]){var getAttribute=$parse(attrs[key]);if(scope.$parent.$watch(getAttribute,function(value){scope.watchData[key]=value}),datepickerEl.attr(cameltoDash(key),"watchData."+key),"datepickerMode"===key){var setAttribute=getAttribute.assign;scope.$watch("watchData."+key,function(value,oldvalue){value!==oldvalue&&setAttribute(scope.$parent,value)})}}}),attrs.dateDisabled&&datepickerEl.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),ngModel.$parsers.unshift(parseDate),scope.dateSelection=function(dt){angular.isDefined(dt)&&(scope.date=dt),ngModel.$setViewValue(scope.date),ngModel.$render(),closeOnDateSelection&&(scope.isOpen=!1,element[0].focus())},element.bind("input change keyup",function(){scope.$apply(function(){scope.date=ngModel.$modelValue})}),ngModel.$render=function(){var date=ngModel.$viewValue?dateFilter(ngModel.$viewValue,dateFormat):"";element.val(date),scope.date=parseDate(ngModel.$modelValue)};var documentClickBind=function(event){scope.isOpen&&event.target!==element[0]&&scope.$apply(function(){scope.isOpen=!1})},keydown=function(evt,noApply){scope.keydown(evt)};element.bind("keydown",keydown),scope.keydown=function(evt){27===evt.which?(evt.preventDefault(),evt.stopPropagation(),scope.close()):40!==evt.which||scope.isOpen||(scope.isOpen=!0)},scope.$watch("isOpen",function(value){value?(scope.$broadcast("datepicker.focus"),scope.position=appendToBody?$position.offset(element):$position.position(element),scope.position.top=scope.position.top+element.prop("offsetHeight"),$document.bind("click",documentClickBind)):$document.unbind("click",documentClickBind)}),scope.select=function(date){if("today"===date){var today=new Date;angular.isDate(ngModel.$modelValue)?(date=new Date(ngModel.$modelValue),date.setFullYear(today.getFullYear(),today.getMonth(),today.getDate())):date=new Date(today.setHours(0,0,0,0))}scope.dateSelection(date)},scope.close=function(){scope.isOpen=!1,element[0].focus()};var $popup=$compile(popupEl)(scope);popupEl.remove(),appendToBody?$document.find("body").append($popup):element.after($popup),scope.$on("$destroy",function(){$popup.remove(),element.unbind("keydown",keydown),$document.unbind("click",documentClickBind)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(scope,element,attrs){element.bind("click",function(event){event.preventDefault(), +event.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function($document){var openScope=null;this.open=function(dropdownScope){openScope||($document.bind("click",closeDropdown),$document.bind("keydown",escapeKeyBind)),openScope&&openScope!==dropdownScope&&(openScope.isOpen=!1),openScope=dropdownScope},this.close=function(dropdownScope){openScope===dropdownScope&&(openScope=null,$document.unbind("click",closeDropdown),$document.unbind("keydown",escapeKeyBind))};var closeDropdown=function(evt){if(openScope){var toggleElement=openScope.getToggleElement();evt&&toggleElement&&toggleElement[0].contains(evt.target)||openScope.$apply(function(){openScope.isOpen=!1})}},escapeKeyBind=function(evt){27===evt.which&&(openScope.focusToggleElement(),closeDropdown())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function($scope,$attrs,$parse,dropdownConfig,dropdownService,$animate){var getIsOpen,self=this,scope=$scope.$new(),openClass=dropdownConfig.openClass,setIsOpen=angular.noop,toggleInvoker=$attrs.onToggle?$parse($attrs.onToggle):angular.noop;this.init=function(element){self.$element=element,$attrs.isOpen&&(getIsOpen=$parse($attrs.isOpen),setIsOpen=getIsOpen.assign,$scope.$watch(getIsOpen,function(value){scope.isOpen=!!value}))},this.toggle=function(open){return scope.isOpen=arguments.length?!!open:!scope.isOpen},this.isOpen=function(){return scope.isOpen},scope.getToggleElement=function(){return self.toggleElement},scope.focusToggleElement=function(){self.toggleElement&&self.toggleElement[0].focus()},scope.$watch("isOpen",function(isOpen,wasOpen){$animate[isOpen?"addClass":"removeClass"](self.$element,openClass),isOpen?(scope.focusToggleElement(),dropdownService.open(scope)):dropdownService.close(scope),setIsOpen($scope,isOpen),angular.isDefined(isOpen)&&isOpen!==wasOpen&&toggleInvoker($scope,{open:!!isOpen})}),$scope.$on("$locationChangeSuccess",function(){scope.isOpen=!1}),$scope.$on("$destroy",function(){scope.$destroy()})}]).directive("dropdown",function(){return{controller:"DropdownController",link:function(scope,element,attrs,dropdownCtrl){dropdownCtrl.init(element)}}}).directive("dropdownToggle",function(){return{require:"?^dropdown",link:function(scope,element,attrs,dropdownCtrl){if(dropdownCtrl){dropdownCtrl.toggleElement=element;var toggleDropdown=function(event){event.preventDefault(),element.hasClass("disabled")||attrs.disabled||scope.$apply(function(){dropdownCtrl.toggle()})};element.bind("click",toggleDropdown),element.attr({"aria-haspopup":!0,"aria-expanded":!1}),scope.$watch(dropdownCtrl.isOpen,function(isOpen){element.attr("aria-expanded",!!isOpen)}),scope.$on("$destroy",function(){element.unbind("click",toggleDropdown)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var stack=[];return{add:function(key,value){stack.push({key:key,value:value})},get:function(key){for(var i=0;i0),checkRemoveBackdrop()})}function checkRemoveBackdrop(){if(backdropDomEl&&-1==backdropIndex()){var backdropScopeRef=backdropScope;removeAfterAnimate(backdropDomEl,backdropScope,150,function(){backdropScopeRef.$destroy(),backdropScopeRef=null}),backdropDomEl=void 0,backdropScope=void 0}}function removeAfterAnimate(domEl,scope,emulateTime,done){function afterAnimating(){afterAnimating.done||(afterAnimating.done=!0,domEl.remove(),done&&done())}scope.animate=!1;var transitionEndEventName=$transition.transitionEndEventName;if(transitionEndEventName){var timeout=$timeout(afterAnimating,emulateTime);domEl.bind(transitionEndEventName,function(){$timeout.cancel(timeout),afterAnimating(),scope.$apply()})}else $timeout(afterAnimating)}var backdropDomEl,backdropScope,OPENED_MODAL_CLASS="modal-open",openedWindows=$$stackedMap.createNew(),$modalStack={};return $rootScope.$watch(backdropIndex,function(newBackdropIndex){backdropScope&&(backdropScope.index=newBackdropIndex)}),$document.bind("keydown",function(evt){var modal;27===evt.which&&(modal=openedWindows.top(),modal&&modal.value.keyboard&&(evt.preventDefault(),$rootScope.$apply(function(){$modalStack.dismiss(modal.key,"escape key press")})))}),$modalStack.open=function(modalInstance,modal){openedWindows.add(modalInstance,{deferred:modal.deferred,modalScope:modal.scope,backdrop:modal.backdrop,keyboard:modal.keyboard});var body=$document.find("body").eq(0),currBackdropIndex=backdropIndex();if(currBackdropIndex>=0&&!backdropDomEl){backdropScope=$rootScope.$new(!0),backdropScope.index=currBackdropIndex;var angularBackgroundDomEl=angular.element("
");angularBackgroundDomEl.attr("backdrop-class",modal.backdropClass),backdropDomEl=$compile(angularBackgroundDomEl)(backdropScope),body.append(backdropDomEl)}var angularDomEl=angular.element("
");angularDomEl.attr({"template-url":modal.windowTemplateUrl,"window-class":modal.windowClass,size:modal.size,index:openedWindows.length()-1,animate:"animate"}).html(modal.content);var modalDomEl=$compile(angularDomEl)(modal.scope);openedWindows.top().value.modalDomEl=modalDomEl,body.append(modalDomEl),body.addClass(OPENED_MODAL_CLASS)},$modalStack.close=function(modalInstance,result){var modalWindow=openedWindows.get(modalInstance);modalWindow&&(modalWindow.value.deferred.resolve(result),removeModalWindow(modalInstance))},$modalStack.dismiss=function(modalInstance,reason){var modalWindow=openedWindows.get(modalInstance);modalWindow&&(modalWindow.value.deferred.reject(reason),removeModalWindow(modalInstance))},$modalStack.dismissAll=function(reason){for(var topModal=this.getTop();topModal;)this.dismiss(topModal.key,reason),topModal=this.getTop()},$modalStack.getTop=function(){return openedWindows.top()},$modalStack}]).provider("$modal",function(){var $modalProvider={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function($injector,$rootScope,$q,$http,$templateCache,$controller,$modalStack){function getTemplatePromise(options){return options.template?$q.when(options.template):$http.get(angular.isFunction(options.templateUrl)?options.templateUrl():options.templateUrl,{cache:$templateCache}).then(function(result){return result.data})}function getResolvePromises(resolves){var promisesArr=[];return angular.forEach(resolves,function(value){(angular.isFunction(value)||angular.isArray(value))&&promisesArr.push($q.when($injector.invoke(value)))}),promisesArr}var $modal={};return $modal.open=function(modalOptions){var modalResultDeferred=$q.defer(),modalOpenedDeferred=$q.defer(),modalInstance={result:modalResultDeferred.promise,opened:modalOpenedDeferred.promise,close:function(result){$modalStack.close(modalInstance,result)},dismiss:function(reason){$modalStack.dismiss(modalInstance,reason)}};if(modalOptions=angular.extend({},$modalProvider.options,modalOptions),modalOptions.resolve=modalOptions.resolve||{},!modalOptions.template&&!modalOptions.templateUrl)throw new Error("One of template or templateUrl options is required.");var templateAndResolvePromise=$q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));return templateAndResolvePromise.then(function(tplAndVars){var modalScope=(modalOptions.scope||$rootScope).$new();modalScope.$close=modalInstance.close,modalScope.$dismiss=modalInstance.dismiss;var ctrlInstance,ctrlLocals={},resolveIter=1;modalOptions.controller&&(ctrlLocals.$scope=modalScope,ctrlLocals.$modalInstance=modalInstance,angular.forEach(modalOptions.resolve,function(value,key){ctrlLocals[key]=tplAndVars[resolveIter++]}),ctrlInstance=$controller(modalOptions.controller,ctrlLocals),modalOptions.controllerAs&&(modalScope[modalOptions.controllerAs]=ctrlInstance)),$modalStack.open(modalInstance,{scope:modalScope,deferred:modalResultDeferred,content:tplAndVars[0],backdrop:modalOptions.backdrop,keyboard:modalOptions.keyboard,backdropClass:modalOptions.backdropClass,windowClass:modalOptions.windowClass,windowTemplateUrl:modalOptions.windowTemplateUrl,size:modalOptions.size})},function(reason){modalResultDeferred.reject(reason)}),templateAndResolvePromise.then(function(){modalOpenedDeferred.resolve(!0)},function(){modalOpenedDeferred.reject(!1)}),modalInstance},$modal}]};return $modalProvider}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function($scope,$attrs,$parse){var self=this,ngModelCtrl={$setViewValue:angular.noop},setNumPages=$attrs.numPages?$parse($attrs.numPages).assign:angular.noop;this.init=function(ngModelCtrl_,config){ngModelCtrl=ngModelCtrl_,this.config=config,ngModelCtrl.$render=function(){self.render()},$attrs.itemsPerPage?$scope.$parent.$watch($parse($attrs.itemsPerPage),function(value){self.itemsPerPage=parseInt(value,10),$scope.totalPages=self.calculateTotalPages()}):this.itemsPerPage=config.itemsPerPage},this.calculateTotalPages=function(){var totalPages=this.itemsPerPage<1?1:Math.ceil($scope.totalItems/this.itemsPerPage);return Math.max(totalPages||0,1)},this.render=function(){$scope.page=parseInt(ngModelCtrl.$viewValue,10)||1},$scope.selectPage=function(page){$scope.page!==page&&page>0&&page<=$scope.totalPages&&(ngModelCtrl.$setViewValue(page),ngModelCtrl.$render())},$scope.getText=function(key){return $scope[key+"Text"]||self.config[key+"Text"]},$scope.noPrevious=function(){return 1===$scope.page},$scope.noNext=function(){return $scope.page===$scope.totalPages},$scope.$watch("totalItems",function(){$scope.totalPages=self.calculateTotalPages()}),$scope.$watch("totalPages",function(value){setNumPages($scope.$parent,value),$scope.page>value?$scope.selectPage(value):ngModelCtrl.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function($parse,paginationConfig){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(scope,element,attrs,ctrls){function makePage(number,text,isActive){return{number:number,text:text,active:isActive}}function getPages(currentPage,totalPages){var pages=[],startPage=1,endPage=totalPages,isMaxSized=angular.isDefined(maxSize)&&totalPages>maxSize;isMaxSized&&(rotate?(startPage=Math.max(currentPage-Math.floor(maxSize/2),1),endPage=startPage+maxSize-1,endPage>totalPages&&(endPage=totalPages,startPage=endPage-maxSize+1)):(startPage=(Math.ceil(currentPage/maxSize)-1)*maxSize+1,endPage=Math.min(startPage+maxSize-1,totalPages)));for(var number=startPage;endPage>=number;number++){var page=makePage(number,number,number===currentPage);pages.push(page)}if(isMaxSized&&!rotate){if(startPage>1){var previousPageSet=makePage(startPage-1,"...",!1);pages.unshift(previousPageSet)}if(totalPages>endPage){var nextPageSet=makePage(endPage+1,"...",!1);pages.push(nextPageSet)}}return pages}var paginationCtrl=ctrls[0],ngModelCtrl=ctrls[1];if(ngModelCtrl){var maxSize=angular.isDefined(attrs.maxSize)?scope.$parent.$eval(attrs.maxSize):paginationConfig.maxSize,rotate=angular.isDefined(attrs.rotate)?scope.$parent.$eval(attrs.rotate):paginationConfig.rotate;scope.boundaryLinks=angular.isDefined(attrs.boundaryLinks)?scope.$parent.$eval(attrs.boundaryLinks):paginationConfig.boundaryLinks,scope.directionLinks=angular.isDefined(attrs.directionLinks)?scope.$parent.$eval(attrs.directionLinks):paginationConfig.directionLinks,paginationCtrl.init(ngModelCtrl,paginationConfig),attrs.maxSize&&scope.$parent.$watch($parse(attrs.maxSize),function(value){maxSize=parseInt(value,10),paginationCtrl.render()});var originalRender=paginationCtrl.render;paginationCtrl.render=function(){originalRender(),scope.page>0&&scope.page<=scope.totalPages&&(scope.pages=getPages(scope.page,scope.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(pagerConfig){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(scope,element,attrs,ctrls){var paginationCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl&&(scope.align=angular.isDefined(attrs.align)?scope.$parent.$eval(attrs.align):pagerConfig.align,paginationCtrl.init(ngModelCtrl,pagerConfig))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function snake_case(name){var regexp=/[A-Z]/g,separator="-";return name.replace(regexp,function(letter,pos){return(pos?separator:"")+letter.toLowerCase()})}var defaultOptions={placement:"top",animation:!0,popupDelay:0},triggerMap={mouseenter:"mouseleave",click:"click",focus:"blur"},globalOptions={};this.options=function(value){angular.extend(globalOptions,value)},this.setTriggers=function(triggers){angular.extend(triggerMap,triggers)},this.$get=["$window","$compile","$timeout","$document","$position","$interpolate",function($window,$compile,$timeout,$document,$position,$interpolate){return function(type,prefix,defaultTriggerShow){function getTriggers(trigger){var show=trigger||options.trigger||defaultTriggerShow,hide=triggerMap[show]||show;return{show:show,hide:hide}}var options=angular.extend({},defaultOptions,globalOptions),directiveName=snake_case(type),startSym=$interpolate.startSymbol(),endSym=$interpolate.endSymbol(),template="
';return{restrict:"EA",compile:function(tElem,tAttrs){var tooltipLinker=$compile(template);return function(scope,element,attrs){function toggleTooltipBind(){ttScope.isOpen?hideTooltipBind():showTooltipBind()}function showTooltipBind(){(!hasEnableExp||scope.$eval(attrs[prefix+"Enable"]))&&(prepareTooltip(),ttScope.popupDelay?popupTimeout||(popupTimeout=$timeout(show,ttScope.popupDelay,!1),popupTimeout.then(function(reposition){reposition()})):show()())}function hideTooltipBind(){scope.$apply(function(){hide()})}function show(){return popupTimeout=null,transitionTimeout&&($timeout.cancel(transitionTimeout),transitionTimeout=null),ttScope.content?(createTooltip(),tooltip.css({top:0,left:0,display:"block"}),ttScope.$digest(),positionTooltip(),ttScope.isOpen=!0,ttScope.$digest(),positionTooltip):angular.noop}function hide(){ttScope.isOpen=!1,$timeout.cancel(popupTimeout),popupTimeout=null,ttScope.animation?transitionTimeout||(transitionTimeout=$timeout(removeTooltip,500)):removeTooltip()}function createTooltip(){tooltip&&removeTooltip(),tooltipLinkedScope=ttScope.$new(),tooltip=tooltipLinker(tooltipLinkedScope,function(tooltip){appendToBody?$document.find("body").append(tooltip):element.after(tooltip)})}function removeTooltip(){transitionTimeout=null,tooltip&&(tooltip.remove(),tooltip=null),tooltipLinkedScope&&(tooltipLinkedScope.$destroy(),tooltipLinkedScope=null)}function prepareTooltip(){prepPlacement(),prepPopupDelay()}function prepPlacement(){var val=attrs[prefix+"Placement"];ttScope.placement=angular.isDefined(val)?val:options.placement}function prepPopupDelay(){var val=attrs[prefix+"PopupDelay"],delay=parseInt(val,10);ttScope.popupDelay=isNaN(delay)?options.popupDelay:delay}function prepTriggers(){var val=attrs[prefix+"Trigger"];unregisterTriggers(),triggers=getTriggers(val),triggers.show===triggers.hide?element.bind(triggers.show,toggleTooltipBind):(element.bind(triggers.show,showTooltipBind),element.bind(triggers.hide,hideTooltipBind))}var tooltip,tooltipLinkedScope,transitionTimeout,popupTimeout,appendToBody=angular.isDefined(options.appendToBody)?options.appendToBody:!1,triggers=getTriggers(void 0),hasEnableExp=angular.isDefined(attrs[prefix+"Enable"]),ttScope=scope.$new(!0),positionTooltip=function(){var ttPosition=$position.positionElements(element,tooltip,ttScope.placement,appendToBody);ttPosition.top+="px",ttPosition.left+="px",tooltip.css(ttPosition)};ttScope.isOpen=!1,attrs.$observe(type,function(val){ttScope.content=val,!val&&ttScope.isOpen&&hide()}),attrs.$observe(prefix+"Title",function(val){ttScope.title=val});var unregisterTriggers=function(){element.unbind(triggers.show,showTooltipBind),element.unbind(triggers.hide,hideTooltipBind)};prepTriggers();var animation=scope.$eval(attrs[prefix+"Animation"]);ttScope.animation=angular.isDefined(animation)?!!animation:options.animation;var appendToBodyVal=scope.$eval(attrs[prefix+"AppendToBody"]);appendToBody=angular.isDefined(appendToBodyVal)?appendToBodyVal:appendToBody,appendToBody&&scope.$on("$locationChangeSuccess",function(){ttScope.isOpen&&hide()}),scope.$on("$destroy",function(){$timeout.cancel(transitionTimeout),$timeout.cancel(popupTimeout),unregisterTriggers(),removeTooltip(),ttScope=null})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function($tooltip){return $tooltip("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function($tooltip){return $tooltip("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function($tooltip){return $tooltip("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function($scope,$attrs,progressConfig){var self=this,animate=angular.isDefined($attrs.animate)?$scope.$parent.$eval($attrs.animate):progressConfig.animate;this.bars=[],$scope.max=angular.isDefined($attrs.max)?$scope.$parent.$eval($attrs.max):progressConfig.max,this.addBar=function(bar,element){animate||element.css({transition:"none"}),this.bars.push(bar),bar.$watch("value",function(value){bar.percent=+(100*value/$scope.max).toFixed(2)}),bar.$on("$destroy",function(){element=null,self.removeBar(bar)})},this.removeBar=function(bar){this.bars.splice(this.bars.indexOf(bar),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(scope,element,attrs,progressCtrl){progressCtrl.addBar(scope,element)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(scope,element,attrs,progressCtrl){progressCtrl.addBar(scope,angular.element(element.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function($scope,$attrs,ratingConfig){var ngModelCtrl={$setViewValue:angular.noop};this.init=function(ngModelCtrl_){ngModelCtrl=ngModelCtrl_,ngModelCtrl.$render=this.render,this.stateOn=angular.isDefined($attrs.stateOn)?$scope.$parent.$eval($attrs.stateOn):ratingConfig.stateOn,this.stateOff=angular.isDefined($attrs.stateOff)?$scope.$parent.$eval($attrs.stateOff):ratingConfig.stateOff;var ratingStates=angular.isDefined($attrs.ratingStates)?$scope.$parent.$eval($attrs.ratingStates):new Array(angular.isDefined($attrs.max)?$scope.$parent.$eval($attrs.max):ratingConfig.max);$scope.range=this.buildTemplateObjects(ratingStates)},this.buildTemplateObjects=function(states){for(var i=0,n=states.length;n>i;i++)states[i]=angular.extend({index:i},{stateOn:this.stateOn,stateOff:this.stateOff},states[i]);return states},$scope.rate=function(value){!$scope.readonly&&value>=0&&value<=$scope.range.length&&(ngModelCtrl.$setViewValue(value),ngModelCtrl.$render())},$scope.enter=function(value){$scope.readonly||($scope.value=value),$scope.onHover({value:value})},$scope.reset=function(){$scope.value=ngModelCtrl.$viewValue,$scope.onLeave()},$scope.onKeydown=function(evt){/(37|38|39|40)/.test(evt.which)&&(evt.preventDefault(),evt.stopPropagation(),$scope.rate($scope.value+(38===evt.which||39===evt.which?1:-1)))},this.render=function(){$scope.value=ngModelCtrl.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(scope,element,attrs,ctrls){var ratingCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl&&ratingCtrl.init(ngModelCtrl)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function($scope){var ctrl=this,tabs=ctrl.tabs=$scope.tabs=[];ctrl.select=function(selectedTab){angular.forEach(tabs,function(tab){tab.active&&tab!==selectedTab&&(tab.active=!1,tab.onDeselect())}),selectedTab.active=!0,selectedTab.onSelect()},ctrl.addTab=function(tab){tabs.push(tab),1===tabs.length?tab.active=!0:tab.active&&ctrl.select(tab)},ctrl.removeTab=function(tab){var index=tabs.indexOf(tab);if(tab.active&&tabs.length>1&&!destroyed){var newActiveIndex=index==tabs.length-1?index-1:index+1;ctrl.select(tabs[newActiveIndex])}tabs.splice(index,1)};var destroyed;$scope.$on("$destroy",function(){destroyed=!0})}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(scope,element,attrs){scope.vertical=angular.isDefined(attrs.vertical)?scope.$parent.$eval(attrs.vertical):!1,scope.justified=angular.isDefined(attrs.justified)?scope.$parent.$eval(attrs.justified):!1}}}).directive("tab",["$parse",function($parse){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(elm,attrs,transclude){return function(scope,elm,attrs,tabsetCtrl){scope.$watch("active",function(active){active&&tabsetCtrl.select(scope)}),scope.disabled=!1,attrs.disabled&&scope.$parent.$watch($parse(attrs.disabled),function(value){scope.disabled=!!value}),scope.select=function(){scope.disabled||(scope.active=!0)},tabsetCtrl.addTab(scope),scope.$on("$destroy",function(){tabsetCtrl.removeTab(scope)}),scope.$transcludeFn=transclude}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(scope,elm,attrs,tabCtrl){scope.$watch("headingElement",function(heading){heading&&(elm.html(""),elm.append(heading))})}}}]).directive("tabContentTransclude",function(){function isTabHeading(node){return node.tagName&&(node.hasAttribute("tab-heading")||node.hasAttribute("data-tab-heading")||"tab-heading"===node.tagName.toLowerCase()||"data-tab-heading"===node.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(scope,elm,attrs){var tab=scope.$eval(attrs.tabContentTransclude);tab.$transcludeFn(tab.$parent,function(contents){angular.forEach(contents,function(node){isTabHeading(node)?tab.headingElement=node:elm.append(node)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function($scope,$attrs,$parse,$log,$locale,timepickerConfig){function getHoursFromTemplate(){var hours=parseInt($scope.hours,10),valid=$scope.showMeridian?hours>0&&13>hours:hours>=0&&24>hours;return valid?($scope.showMeridian&&(12===hours&&(hours=0),$scope.meridian===meridians[1]&&(hours+=12)),hours):void 0}function getMinutesFromTemplate(){var minutes=parseInt($scope.minutes,10);return minutes>=0&&60>minutes?minutes:void 0}function pad(value){return angular.isDefined(value)&&value.toString().length<2?"0"+value:value}function refresh(keyboardChange){makeValid(),ngModelCtrl.$setViewValue(new Date(selected)),updateTemplate(keyboardChange)}function makeValid(){ngModelCtrl.$setValidity("time",!0),$scope.invalidHours=!1,$scope.invalidMinutes=!1}function updateTemplate(keyboardChange){var hours=selected.getHours(),minutes=selected.getMinutes();$scope.showMeridian&&(hours=0===hours||12===hours?12:hours%12),$scope.hours="h"===keyboardChange?hours:pad(hours),$scope.minutes="m"===keyboardChange?minutes:pad(minutes),$scope.meridian=selected.getHours()<12?meridians[0]:meridians[1]}function addMinutes(minutes){var dt=new Date(selected.getTime()+6e4*minutes);selected.setHours(dt.getHours(),dt.getMinutes()),refresh()}var selected=new Date,ngModelCtrl={$setViewValue:angular.noop},meridians=angular.isDefined($attrs.meridians)?$scope.$parent.$eval($attrs.meridians):timepickerConfig.meridians||$locale.DATETIME_FORMATS.AMPMS;this.init=function(ngModelCtrl_,inputs){ngModelCtrl=ngModelCtrl_,ngModelCtrl.$render=this.render;var hoursInputEl=inputs.eq(0),minutesInputEl=inputs.eq(1),mousewheel=angular.isDefined($attrs.mousewheel)?$scope.$parent.$eval($attrs.mousewheel):timepickerConfig.mousewheel;mousewheel&&this.setupMousewheelEvents(hoursInputEl,minutesInputEl),$scope.readonlyInput=angular.isDefined($attrs.readonlyInput)?$scope.$parent.$eval($attrs.readonlyInput):timepickerConfig.readonlyInput,this.setupInputEvents(hoursInputEl,minutesInputEl)};var hourStep=timepickerConfig.hourStep;$attrs.hourStep&&$scope.$parent.$watch($parse($attrs.hourStep),function(value){hourStep=parseInt(value,10)});var minuteStep=timepickerConfig.minuteStep;$attrs.minuteStep&&$scope.$parent.$watch($parse($attrs.minuteStep),function(value){minuteStep=parseInt(value,10)}),$scope.showMeridian=timepickerConfig.showMeridian,$attrs.showMeridian&&$scope.$parent.$watch($parse($attrs.showMeridian),function(value){if($scope.showMeridian=!!value,ngModelCtrl.$error.time){var hours=getHoursFromTemplate(),minutes=getMinutesFromTemplate();angular.isDefined(hours)&&angular.isDefined(minutes)&&(selected.setHours(hours),refresh())}else updateTemplate()}),this.setupMousewheelEvents=function(hoursInputEl,minutesInputEl){var isScrollingUp=function(e){e.originalEvent&&(e=e.originalEvent);var delta=e.wheelDelta?e.wheelDelta:-e.deltaY;return e.detail||delta>0};hoursInputEl.bind("mousewheel wheel",function(e){$scope.$apply(isScrollingUp(e)?$scope.incrementHours():$scope.decrementHours()),e.preventDefault()}),minutesInputEl.bind("mousewheel wheel",function(e){$scope.$apply(isScrollingUp(e)?$scope.incrementMinutes():$scope.decrementMinutes()),e.preventDefault()})},this.setupInputEvents=function(hoursInputEl,minutesInputEl){if($scope.readonlyInput)return $scope.updateHours=angular.noop,void($scope.updateMinutes=angular.noop);var invalidate=function(invalidHours,invalidMinutes){ngModelCtrl.$setViewValue(null),ngModelCtrl.$setValidity("time",!1),angular.isDefined(invalidHours)&&($scope.invalidHours=invalidHours),angular.isDefined(invalidMinutes)&&($scope.invalidMinutes=invalidMinutes)};$scope.updateHours=function(){var hours=getHoursFromTemplate();angular.isDefined(hours)?(selected.setHours(hours),refresh("h")):invalidate(!0)},hoursInputEl.bind("blur",function(e){!$scope.invalidHours&&$scope.hours<10&&$scope.$apply(function(){$scope.hours=pad($scope.hours)})}),$scope.updateMinutes=function(){var minutes=getMinutesFromTemplate();angular.isDefined(minutes)?(selected.setMinutes(minutes),refresh("m")):invalidate(void 0,!0)},minutesInputEl.bind("blur",function(e){!$scope.invalidMinutes&&$scope.minutes<10&&$scope.$apply(function(){$scope.minutes=pad($scope.minutes)})})},this.render=function(){var date=ngModelCtrl.$modelValue?new Date(ngModelCtrl.$modelValue):null;isNaN(date)?(ngModelCtrl.$setValidity("time",!1),$log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(date&&(selected=date),makeValid(),updateTemplate())},$scope.incrementHours=function(){addMinutes(60*hourStep)},$scope.decrementHours=function(){addMinutes(60*-hourStep)},$scope.incrementMinutes=function(){addMinutes(minuteStep)},$scope.decrementMinutes=function(){addMinutes(-minuteStep)},$scope.toggleMeridian=function(){addMinutes(720*(selected.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(scope,element,attrs,ctrls){var timepickerCtrl=ctrls[0],ngModelCtrl=ctrls[1];ngModelCtrl&&timepickerCtrl.init(ngModelCtrl,element.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function($parse){var TYPEAHEAD_REGEXP=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; +return{parse:function(input){var match=input.match(TYPEAHEAD_REGEXP);if(!match)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+input+'".');return{itemName:match[3],source:$parse(match[4]),viewMapper:$parse(match[2]||match[1]),modelMapper:$parse(match[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function($compile,$parse,$q,$timeout,$document,$position,typeaheadParser){var HOT_KEYS=[9,13,27,38,40];return{require:"ngModel",link:function(originalScope,element,attrs,modelCtrl){var hasFocus,minSearch=originalScope.$eval(attrs.typeaheadMinLength)||1,waitTime=originalScope.$eval(attrs.typeaheadWaitMs)||0,isEditable=originalScope.$eval(attrs.typeaheadEditable)!==!1,isLoadingSetter=$parse(attrs.typeaheadLoading).assign||angular.noop,onSelectCallback=$parse(attrs.typeaheadOnSelect),inputFormatter=attrs.typeaheadInputFormatter?$parse(attrs.typeaheadInputFormatter):void 0,appendToBody=attrs.typeaheadAppendToBody?originalScope.$eval(attrs.typeaheadAppendToBody):!1,focusFirst=originalScope.$eval(attrs.typeaheadFocusFirst)!==!1,$setModelValue=$parse(attrs.ngModel).assign,parserResult=typeaheadParser.parse(attrs.typeahead),scope=originalScope.$new();originalScope.$on("$destroy",function(){scope.$destroy()});var popupId="typeahead-"+scope.$id+"-"+Math.floor(1e4*Math.random());element.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":popupId});var popUpEl=angular.element("
");popUpEl.attr({id:popupId,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(attrs.typeaheadTemplateUrl)&&popUpEl.attr("template-url",attrs.typeaheadTemplateUrl);var resetMatches=function(){scope.matches=[],scope.activeIdx=-1,element.attr("aria-expanded",!1)},getMatchId=function(index){return popupId+"-option-"+index};scope.$watch("activeIdx",function(index){0>index?element.removeAttr("aria-activedescendant"):element.attr("aria-activedescendant",getMatchId(index))});var getMatchesAsync=function(inputValue){var locals={$viewValue:inputValue};isLoadingSetter(originalScope,!0),$q.when(parserResult.source(originalScope,locals)).then(function(matches){var onCurrentRequest=inputValue===modelCtrl.$viewValue;if(onCurrentRequest&&hasFocus)if(matches.length>0){scope.activeIdx=focusFirst?0:-1,scope.matches.length=0;for(var i=0;i=minSearch?waitTime>0?(cancelPreviousTimeout(),scheduleSearchWithTimeout(inputValue)):getMatchesAsync(inputValue):(isLoadingSetter(originalScope,!1),cancelPreviousTimeout(),resetMatches()),isEditable?inputValue:inputValue?void modelCtrl.$setValidity("editable",!1):(modelCtrl.$setValidity("editable",!0),inputValue)}),modelCtrl.$formatters.push(function(modelValue){var candidateViewValue,emptyViewValue,locals={};return inputFormatter?(locals.$model=modelValue,inputFormatter(originalScope,locals)):(locals[parserResult.itemName]=modelValue,candidateViewValue=parserResult.viewMapper(originalScope,locals),locals[parserResult.itemName]=void 0,emptyViewValue=parserResult.viewMapper(originalScope,locals),candidateViewValue!==emptyViewValue?candidateViewValue:modelValue)}),scope.select=function(activeIdx){var model,item,locals={};locals[parserResult.itemName]=item=scope.matches[activeIdx].model,model=parserResult.modelMapper(originalScope,locals),$setModelValue(originalScope,model),modelCtrl.$setValidity("editable",!0),onSelectCallback(originalScope,{$item:item,$model:model,$label:parserResult.viewMapper(originalScope,locals)}),resetMatches(),$timeout(function(){element[0].focus()},0,!1)},element.bind("keydown",function(evt){0!==scope.matches.length&&-1!==HOT_KEYS.indexOf(evt.which)&&(-1!=scope.activeIdx||13!==evt.which&&9!==evt.which)&&(evt.preventDefault(),40===evt.which?(scope.activeIdx=(scope.activeIdx+1)%scope.matches.length,scope.$digest()):38===evt.which?(scope.activeIdx=(scope.activeIdx>0?scope.activeIdx:scope.matches.length)-1,scope.$digest()):13===evt.which||9===evt.which?scope.$apply(function(){scope.select(scope.activeIdx)}):27===evt.which&&(evt.stopPropagation(),resetMatches(),scope.$digest()))}),element.bind("blur",function(evt){hasFocus=!1});var dismissClickHandler=function(evt){element[0]!==evt.target&&(resetMatches(),scope.$digest())};$document.bind("click",dismissClickHandler),originalScope.$on("$destroy",function(){$document.unbind("click",dismissClickHandler),appendToBody&&$popup.remove()});var $popup=$compile(popUpEl)(scope);appendToBody?$document.find("body").append($popup):element.after($popup)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(scope,element,attrs){scope.templateUrl=attrs.templateUrl,scope.isOpen=function(){return scope.matches.length>0},scope.isActive=function(matchIdx){return scope.active==matchIdx},scope.selectActive=function(matchIdx){scope.active=matchIdx},scope.selectMatch=function(activeIdx){scope.select({activeIdx:activeIdx})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function($http,$templateCache,$compile,$parse){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(scope,element,attrs){var tplUrl=$parse(attrs.templateUrl)(scope.$parent)||"template/typeahead/typeahead-match.html";$http.get(tplUrl,{cache:$templateCache}).success(function(tplContent){element.replaceWith($compile(tplContent.trim())(scope))})}}}]).filter("typeaheadHighlight",function(){function escapeRegexp(queryToEscape){return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(matchItem,query){return query?(""+matchItem).replace(new RegExp(escapeRegexp(query),"gi"),"$&"):matchItem}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/accordion/accordion-group.html",'
\n
\n

\n {{heading}}\n

\n
\n
\n
\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/accordion/accordion.html",'
')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/carousel/slide.html","
\n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/datepicker/datepicker.html",'
\n \n \n \n
')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
{{label.abbr}}
{{ weekNumbers[$index] }}\n \n
\n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/tooltip/tooltip-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/popover/popover.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/progressbar/bar.html",'
')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/progressbar/progress.html",'
')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/progressbar/progressbar.html",'
\n
\n
')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/tabs/tab.html",'
  • \n {{heading}}\n
  • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/tabs/tabset.html",'
    \n \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/timepicker/timepicker.html",'\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')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function($templateCache){$templateCache.put("template/typeahead/typeahead-popup.html",'\n')}]); + +},{}],13:[function(require,module,exports){ +!function(document,window,angular){"use strict";angular.module("googlechart",[]).value("googleChartApiConfig",{version:"1",optionalSettings:{packages:["corechart"]}}).provider("googleJsapiUrl",function(){var protocol="https:",url="//www.google.com/jsapi";this.setProtocol=function(newProtocol){protocol=newProtocol},this.setUrl=function(newUrl){url=newUrl},this.$get=function(){return(protocol?protocol:"")+url}}).factory("googleChartApiPromise",["$rootScope","$q","googleChartApiConfig","googleJsapiUrl",function($rootScope,$q,apiConfig,googleJsapiUrl){var apiReady=$q.defer(),onLoad=function(){var settings={callback:function(){var oldCb=apiConfig.optionalSettings.callback;$rootScope.$apply(function(){apiReady.resolve()}),angular.isFunction(oldCb)&&oldCb.call(this)}};settings=angular.extend({},apiConfig.optionalSettings,settings),window.google.load("visualization",apiConfig.version,settings)},head=document.getElementsByTagName("head")[0],script=document.createElement("script");return script.setAttribute("type","text/javascript"),script.src=googleJsapiUrl,script.addEventListener?script.addEventListener("load",onLoad,!1):script.onreadystatechange=function(){("loaded"===script.readyState||"complete"===script.readyState)&&(script.onreadystatechange=null,onLoad())},head.appendChild(script),apiReady.promise}]).directive("googleChart",["$timeout","$window","$rootScope","googleChartApiPromise",function($timeout,$window,$rootScope,googleChartApiPromise){return{restrict:"A",scope:{beforeDraw:"&",chart:"=chart",onReady:"&",onSelect:"&",select:"&"},link:function($scope,$elm,$attrs){function applyFormat(formatType,formatClass,dataTable){if("undefined"!=typeof $scope.chart.formatters[formatType]){if(!angular.equals($scope.chart.formatters[formatType],$scope.oldChartFormatters[formatType]))if($scope.oldChartFormatters[formatType]=$scope.chart.formatters[formatType],$scope.formatters[formatType]=[],"color"===formatType)for(var cIdx=0;cIdx<$scope.chart.formatters[formatType].length;cIdx++){var colorFormat=new formatClass;for(i=0;i<$scope.chart.formatters[formatType][cIdx].formats.length;i++){var data=$scope.chart.formatters[formatType][cIdx].formats[i];"undefined"!=typeof data.fromBgColor&&"undefined"!=typeof data.toBgColor?colorFormat.addGradientRange(data.from,data.to,data.color,data.fromBgColor,data.toBgColor):colorFormat.addRange(data.from,data.to,data.color,data.bgcolor)}$scope.formatters[formatType].push(colorFormat)}else for(var i=0;i<$scope.chart.formatters[formatType].length;i++)$scope.formatters[formatType].push(new formatClass($scope.chart.formatters[formatType][i]));for(i=0;i<$scope.formatters[formatType].length;i++)$scope.chart.formatters[formatType][i].columnNum=fullDateThreshold;if(showFullDate?element.text(momentInstance.format(fullDateFormat)):element.text(momentInstance.from(getNow(),withoutSuffix)),titleFormat&&!element.attr("title")&&element.attr("title",momentInstance.local().format(titleFormat)),!showFullDate){var howOld=Math.abs(getNow().diff(momentInstance,"minute")),secondsUntilUpdate=3600;1>howOld?secondsUntilUpdate=1:60>howOld?secondsUntilUpdate=30:180>howOld&&(secondsUntilUpdate=300),activeTimeout=$window.setTimeout(function(){updateTime(momentInstance)},1e3*secondsUntilUpdate)}}function updateDateTimeAttr(value){isTimeElement&&element.attr("datetime",value)}function updateMoment(){if(cancelTimer(),currentValue){var momentValue=amMoment.preprocessDate(currentValue,preprocess,currentFormat);updateTime(momentValue),updateDateTimeAttr(momentValue.toISOString())}}var currentValue,currentFrom,activeTimeout=null,currentFormat=angularMomentConfig.format,withoutSuffix=amTimeAgoConfig.withoutSuffix,titleFormat=amTimeAgoConfig.titleFormat,fullDateThreshold=amTimeAgoConfig.fullDateThreshold,fullDateFormat=amTimeAgoConfig.fullDateFormat,localDate=(new Date).getTime(),preprocess=angularMomentConfig.preprocess,modelName=attr.amTimeAgo,isTimeElement="TIME"===element[0].nodeName.toUpperCase();scope.$watch(modelName,function(value){return"undefined"==typeof value||null===value||""===value?(cancelTimer(),void(currentValue&&(element.text(""),updateDateTimeAttr(""),currentValue=null))):(currentValue=value,void updateMoment())}),angular.isDefined(attr.amFrom)&&scope.$watch(attr.amFrom,function(value){currentFrom="undefined"==typeof value||null===value||""===value?null:moment(value),updateMoment()}),angular.isDefined(attr.amWithoutSuffix)&&scope.$watch(attr.amWithoutSuffix,function(value){"boolean"==typeof value?(withoutSuffix=value,updateMoment()):withoutSuffix=amTimeAgoConfig.withoutSuffix}),attr.$observe("amFormat",function(format){"undefined"!=typeof format&&(currentFormat=format,updateMoment())}),attr.$observe("amPreprocess",function(newValue){preprocess=newValue,updateMoment()}),attr.$observe("amFullDateThreshold",function(newValue){fullDateThreshold=newValue,updateMoment()}),attr.$observe("amFullDateFormat",function(newValue){fullDateFormat=newValue,updateMoment()}),scope.$on("$destroy",function(){cancelTimer()}),scope.$on("amMoment:localeChanged",function(){updateMoment()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(moment,$rootScope,$log,angularMomentConfig){this.preprocessors={utc:moment.utc,unix:moment.unix},this.changeLocale=function(locale,customization){var result=moment.locale(locale,customization);return angular.isDefined(locale)&&$rootScope.$broadcast("amMoment:localeChanged"),result},this.changeTimezone=function(timezone){angularMomentConfig.timezone=timezone,$rootScope.$broadcast("amMoment:timezoneChanged")},this.preprocessDate=function(value,preprocess,format){return angular.isUndefined(preprocess)&&(preprocess=angularMomentConfig.preprocess),this.preprocessors[preprocess]?this.preprocessors[preprocess](value,format):(preprocess&&$log.warn("angular-moment: Ignoring unsupported value for preprocess: "+preprocess),!isNaN(parseFloat(value))&&isFinite(value)?moment(parseInt(value,10)):moment(value,format))},this.applyTimezone=function(aMoment,timezone){return(timezone=timezone||angularMomentConfig.timezone)?(timezone.match(/Z|[+-]\d\d:?\d\d/gi)?aMoment=aMoment.utcOffset(timezone):aMoment.tz?aMoment=aMoment.tz(timezone):$log.warn("angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?"),aMoment):aMoment}}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(moment,amMoment,angularMomentConfig){function amCalendarFilter(value,preprocess,timezone){if("undefined"==typeof value||null===value)return"";value=amMoment.preprocessDate(value,preprocess);var date=moment(value);return date.isValid()?amMoment.applyTimezone(date,timezone).calendar():""}return amCalendarFilter.$stateful=angularMomentConfig.statefulFilters,amCalendarFilter}]).filter("amDifference",["moment","amMoment","angularMomentConfig",function(moment,amMoment,angularMomentConfig){function amDifferenceFilter(value,otherValue,unit,usePrecision,preprocessValue,preprocessOtherValue){if("undefined"==typeof value||null===value)return"";value=amMoment.preprocessDate(value,preprocessValue);var date=moment(value);if(!date.isValid())return"";var date2;if("undefined"==typeof otherValue||null===otherValue)date2=moment();else if(otherValue=amMoment.preprocessDate(otherValue,preprocessOtherValue),date2=moment(otherValue),!date2.isValid())return"";return amMoment.applyTimezone(date).diff(amMoment.applyTimezone(date2),unit,usePrecision)}return amDifferenceFilter.$stateful=angularMomentConfig.statefulFilters,amDifferenceFilter}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(moment,amMoment,angularMomentConfig){function amDateFormatFilter(value,format,preprocess,timezone){if("undefined"==typeof value||null===value)return"";value=amMoment.preprocessDate(value,preprocess);var date=moment(value);return date.isValid()?amMoment.applyTimezone(date,timezone).format(format):""}return amDateFormatFilter.$stateful=angularMomentConfig.statefulFilters,amDateFormatFilter}]).filter("amDurationFormat",["moment","angularMomentConfig",function(moment,angularMomentConfig){function amDurationFormatFilter(value,format,suffix){return"undefined"==typeof value||null===value?"":moment.duration(value,format).humanize(suffix)}return amDurationFormatFilter.$stateful=angularMomentConfig.statefulFilters,amDurationFormatFilter}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(moment,amMoment,angularMomentConfig){function amTimeAgoFilter(value,preprocess,suffix,from){var date,dateFrom;return"undefined"==typeof value||null===value?"":(value=amMoment.preprocessDate(value,preprocess),date=moment(value),date.isValid()?(dateFrom=moment(from),"undefined"!=typeof from&&dateFrom.isValid()?amMoment.applyTimezone(date).from(dateFrom,suffix):amMoment.applyTimezone(date).fromNow(suffix)):"")}return amTimeAgoFilter.$stateful=angularMomentConfig.statefulFilters,amTimeAgoFilter}])}"function"==typeof define&&define.amd?define(["angular","moment"],angularMoment):"undefined"!=typeof module&&module&&module.exports?(angularMoment(angular,require("moment")),module.exports="angularMoment"):angularMoment(angular,("undefined"!=typeof global?global:window).moment)}(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"moment":26}],15:[function(require,module,exports){ +!function(window,angular,undefined){"use strict";function $RouteProvider(){function inherit(parent,extra){return angular.extend(Object.create(parent),extra)}function pathRegExp(path,opts){var insensitive=opts.caseInsensitiveMatch,ret={originalPath:path,regexp:path},keys=ret.keys=[];return path=path.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(_,slash,key,option){var optional="?"===option?option:null,star="*"===option?option:null;return keys.push({name:key,optional:!!optional}),slash=slash||"",""+(optional?"":slash)+"(?:"+(optional?slash:"")+(star&&"(.+?)"||"([^/]+)")+(optional||"")+")"+(optional||"")}).replace(/([\/$\*])/g,"\\$1"),ret.regexp=new RegExp("^"+path+"$",insensitive?"i":""),ret}var routes={};this.when=function(path,route){var routeCopy=angular.copy(route);if(angular.isUndefined(routeCopy.reloadOnSearch)&&(routeCopy.reloadOnSearch=!0),angular.isUndefined(routeCopy.caseInsensitiveMatch)&&(routeCopy.caseInsensitiveMatch=this.caseInsensitiveMatch),routes[path]=angular.extend(routeCopy,path&&pathRegExp(path,routeCopy)),path){var redirectPath="/"==path[path.length-1]?path.substr(0,path.length-1):path+"/";routes[redirectPath]=angular.extend({redirectTo:path},pathRegExp(redirectPath,routeCopy))}return this},this.caseInsensitiveMatch=!1,this.otherwise=function(params){return"string"==typeof params&&(params={redirectTo:params}),this.when(null,params),this},this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function($rootScope,$location,$routeParams,$q,$injector,$templateRequest,$sce){function switchRouteMatcher(on,route){var keys=route.keys,params={};if(!route.regexp)return null;var m=route.regexp.exec(on);if(!m)return null;for(var i=1,len=m.length;len>i;++i){var key=keys[i-1],val=m[i];key&&val&&(params[key.name]=val)}return params}function prepareRoute($locationEvent){var lastRoute=$route.current;preparedRoute=parseRoute(),preparedRouteIsUpdateOnly=preparedRoute&&lastRoute&&preparedRoute.$$route===lastRoute.$$route&&angular.equals(preparedRoute.pathParams,lastRoute.pathParams)&&!preparedRoute.reloadOnSearch&&!forceReload,preparedRouteIsUpdateOnly||!lastRoute&&!preparedRoute||$rootScope.$broadcast("$routeChangeStart",preparedRoute,lastRoute).defaultPrevented&&$locationEvent&&$locationEvent.preventDefault()}function commitRoute(){var lastRoute=$route.current,nextRoute=preparedRoute;preparedRouteIsUpdateOnly?(lastRoute.params=nextRoute.params,angular.copy(lastRoute.params,$routeParams),$rootScope.$broadcast("$routeUpdate",lastRoute)):(nextRoute||lastRoute)&&(forceReload=!1,$route.current=nextRoute,nextRoute&&nextRoute.redirectTo&&(angular.isString(nextRoute.redirectTo)?$location.path(interpolate(nextRoute.redirectTo,nextRoute.params)).search(nextRoute.params).replace():$location.url(nextRoute.redirectTo(nextRoute.pathParams,$location.path(),$location.search())).replace()),$q.when(nextRoute).then(function(){if(nextRoute){var template,templateUrl,locals=angular.extend({},nextRoute.resolve);return angular.forEach(locals,function(value,key){locals[key]=angular.isString(value)?$injector.get(value):$injector.invoke(value,null,null,key)}),angular.isDefined(template=nextRoute.template)?angular.isFunction(template)&&(template=template(nextRoute.params)):angular.isDefined(templateUrl=nextRoute.templateUrl)&&(angular.isFunction(templateUrl)&&(templateUrl=templateUrl(nextRoute.params)),angular.isDefined(templateUrl)&&(nextRoute.loadedTemplateUrl=$sce.valueOf(templateUrl),template=$templateRequest(templateUrl))),angular.isDefined(template)&&(locals.$template=template),$q.all(locals)}}).then(function(locals){nextRoute==$route.current&&(nextRoute&&(nextRoute.locals=locals,angular.copy(nextRoute.params,$routeParams)),$rootScope.$broadcast("$routeChangeSuccess",nextRoute,lastRoute))},function(error){nextRoute==$route.current&&$rootScope.$broadcast("$routeChangeError",nextRoute,lastRoute,error)}))}function parseRoute(){var params,match;return angular.forEach(routes,function(route,path){!match&&(params=switchRouteMatcher($location.path(),route))&&(match=inherit(route,{params:angular.extend({},$location.search(),params),pathParams:params}),match.$$route=route)}),match||routes[null]&&inherit(routes[null],{params:{},pathParams:{}})}function interpolate(string,params){var result=[];return angular.forEach((string||"").split(":"),function(segment,i){if(0===i)result.push(segment);else{var segmentMatch=segment.match(/(\w+)(?:[?*])?(.*)/),key=segmentMatch[1];result.push(params[key]),result.push(segmentMatch[2]||""),delete params[key]}}),result.join("")}var preparedRoute,preparedRouteIsUpdateOnly,forceReload=!1,$route={routes:routes,reload:function(){forceReload=!0,$rootScope.$evalAsync(function(){prepareRoute(),commitRoute()})},updateParams:function(newParams){if(!this.current||!this.current.$$route)throw $routeMinErr("norout","Tried updating route when with no current route");newParams=angular.extend({},this.current.params,newParams),$location.path(interpolate(this.current.$$route.originalPath,newParams)),$location.search(newParams)}};return $rootScope.$on("$locationChangeStart",prepareRoute),$rootScope.$on("$locationChangeSuccess",commitRoute),$route}]}function $RouteParamsProvider(){this.$get=function(){return{}}}function ngViewFactory($route,$anchorScroll,$animate){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(scope,$element,attr,ctrl,$transclude){function cleanupLastView(){previousLeaveAnimation&&($animate.cancel(previousLeaveAnimation),previousLeaveAnimation=null),currentScope&&(currentScope.$destroy(),currentScope=null),currentElement&&(previousLeaveAnimation=$animate.leave(currentElement),previousLeaveAnimation.then(function(){previousLeaveAnimation=null}),currentElement=null)}function update(){var locals=$route.current&&$route.current.locals,template=locals&&locals.$template;if(angular.isDefined(template)){var newScope=scope.$new(),current=$route.current,clone=$transclude(newScope,function(clone){$animate.enter(clone,null,currentElement||$element).then(function(){!angular.isDefined(autoScrollExp)||autoScrollExp&&!scope.$eval(autoScrollExp)||$anchorScroll()}),cleanupLastView()});currentElement=clone,currentScope=current.scope=newScope,currentScope.$emit("$viewContentLoaded"),currentScope.$eval(onloadExp)}else cleanupLastView()}var currentScope,currentElement,previousLeaveAnimation,autoScrollExp=attr.autoscroll,onloadExp=attr.onload||"";scope.$on("$routeChangeSuccess",update),update()}}}function ngViewFillContentFactory($compile,$controller,$route){return{restrict:"ECA",priority:-400,link:function(scope,$element){var current=$route.current,locals=current.locals;$element.html(locals.$template);var link=$compile($element.contents());if(current.controller){locals.$scope=scope;var controller=$controller(current.controller,locals);current.controllerAs&&(scope[current.controllerAs]=controller),$element.data("$ngControllerController",controller),$element.children().data("$ngControllerController",controller)}link(scope)}}}var ngRouteModule=angular.module("ngRoute",["ng"]).provider("$route",$RouteProvider),$routeMinErr=angular.$$minErr("ngRoute");ngRouteModule.provider("$routeParams",$RouteParamsProvider),ngRouteModule.directive("ngView",ngViewFactory),ngRouteModule.directive("ngView",ngViewFillContentFactory),ngViewFactory.$inject=["$route","$anchorScroll","$animate"],ngViewFillContentFactory.$inject=["$compile","$controller","$route"]}(window,window.angular); + +},{}],16:[function(require,module,exports){ +require("./angular-route"),module.exports="ngRoute"; + +},{"./angular-route":15}],17:[function(require,module,exports){ +!function(window,document,undefined){"use strict";function minErr(module,ErrorConstructor){return ErrorConstructor=ErrorConstructor||Error,function(){var paramPrefix,i,SKIP_INDEXES=2,templateArgs=arguments,code=templateArgs[0],message="["+(module?module+":":"")+code+"] ",template=templateArgs[1];for(message+=template.replace(/\{\d+\}/g,function(match){var index=+match.slice(1,-1),shiftedIndex=index+SKIP_INDEXES;return shiftedIndex0&&length-1 in obj}function forEach(obj,iterator,context){var key,length;if(obj)if(isFunction(obj))for(key in obj)"prototype"==key||"length"==key||"name"==key||obj.hasOwnProperty&&!obj.hasOwnProperty(key)||iterator.call(context,obj[key],key,obj);else if(isArray(obj)||isArrayLike(obj)){var isPrimitive="object"!=typeof obj;for(key=0,length=obj.length;length>key;key++)(isPrimitive||key in obj)&&iterator.call(context,obj[key],key,obj)}else if(obj.forEach&&obj.forEach!==forEach)obj.forEach(iterator,context,obj);else if(isBlankObject(obj))for(key in obj)iterator.call(context,obj[key],key,obj);else if("function"==typeof obj.hasOwnProperty)for(key in obj)obj.hasOwnProperty(key)&&iterator.call(context,obj[key],key,obj);else for(key in obj)hasOwnProperty.call(obj,key)&&iterator.call(context,obj[key],key,obj);return obj}function forEachSorted(obj,iterator,context){for(var keys=Object.keys(obj).sort(),i=0;ii;++i){var obj=objs[i];if(isObject(obj)||isFunction(obj))for(var keys=Object.keys(obj),j=0,jj=keys.length;jj>j;j++){var key=keys[j],src=obj[key];deep&&isObject(src)?isDate(src)?dst[key]=new Date(src.valueOf()):isRegExp(src)?dst[key]=new RegExp(src):(isObject(dst[key])||(dst[key]=isArray(src)?[]:{}),baseExtend(dst[key],[src],!0)):dst[key]=src}}return setHashKey(dst,h),dst}function extend(dst){return baseExtend(dst,slice.call(arguments,1),!1)}function merge(dst){return baseExtend(dst,slice.call(arguments,1),!0)}function toInt(str){return parseInt(str,10)}function inherit(parent,extra){return extend(Object.create(parent),extra)}function noop(){}function identity($){return $}function valueFn(value){return function(){return value}}function hasCustomToString(obj){return isFunction(obj.toString)&&obj.toString!==Object.prototype.toString}function isUndefined(value){return"undefined"==typeof value}function isDefined(value){return"undefined"!=typeof value}function isObject(value){return null!==value&&"object"==typeof value}function isBlankObject(value){return null!==value&&"object"==typeof value&&!getPrototypeOf(value)}function isString(value){return"string"==typeof value}function isNumber(value){return"number"==typeof value}function isDate(value){return"[object Date]"===toString.call(value)}function isFunction(value){return"function"==typeof value}function isRegExp(value){return"[object RegExp]"===toString.call(value)}function isWindow(obj){return obj&&obj.window===obj}function isScope(obj){return obj&&obj.$evalAsync&&obj.$watch}function isFile(obj){return"[object File]"===toString.call(obj)}function isFormData(obj){return"[object FormData]"===toString.call(obj)}function isBlob(obj){return"[object Blob]"===toString.call(obj)}function isBoolean(value){return"boolean"==typeof value}function isPromiseLike(obj){return obj&&isFunction(obj.then)}function isTypedArray(value){return TYPED_ARRAY_REGEXP.test(toString.call(value))}function isElement(node){return!(!node||!(node.nodeName||node.prop&&node.attr&&node.find))}function makeMap(str){var i,obj={},items=str.split(",");for(i=0;i=0&&array.splice(index,1),index}function copy(source,destination,stackSource,stackDest){if(isWindow(source)||isScope(source))throw ngMinErr("cpws","Can't copy! Making copies of Window or Scope instances is not supported.");if(isTypedArray(destination))throw ngMinErr("cpta","Can't copy! TypedArray destination cannot be mutated.");if(destination){if(source===destination)throw ngMinErr("cpi","Can't copy! Source and destination are identical.");stackSource=stackSource||[],stackDest=stackDest||[],isObject(source)&&(stackSource.push(source),stackDest.push(destination));var key;if(isArray(source)){destination.length=0;for(var i=0;ii;i++)dst[i]=src[i]}else if(isObject(src)){dst=dst||{};for(var key in src)("$"!==key.charAt(0)||"$"!==key.charAt(1))&&(dst[key]=src[key])}return dst||src}function equals(o1,o2){if(o1===o2)return!0;if(null===o1||null===o2)return!1;if(o1!==o1&&o2!==o2)return!0;var length,key,keySet,t1=typeof o1,t2=typeof o2;if(t1==t2&&"object"==t1){if(!isArray(o1)){if(isDate(o1))return isDate(o2)?equals(o1.getTime(),o2.getTime()):!1;if(isRegExp(o1))return isRegExp(o2)?o1.toString()==o2.toString():!1;if(isScope(o1)||isScope(o2)||isWindow(o1)||isWindow(o2)||isArray(o2)||isDate(o2)||isRegExp(o2))return!1;keySet=createMap();for(key in o1)if("$"!==key.charAt(0)&&!isFunction(o1[key])){if(!equals(o1[key],o2[key]))return!1;keySet[key]=!0}for(key in o2)if(!(key in keySet||"$"===key.charAt(0)||o2[key]===undefined||isFunction(o2[key])))return!1;return!0}if(!isArray(o2))return!1;if((length=o1.length)==o2.length){for(key=0;length>key;key++)if(!equals(o1[key],o2[key]))return!1;return!0}}return!1}function concat(array1,array2,index){return array1.concat(slice.call(array2,index))}function sliceArgs(args,startIndex){return slice.call(args,startIndex||0)}function bind(self,fn){var curryArgs=arguments.length>2?sliceArgs(arguments,2):[];return!isFunction(fn)||fn instanceof RegExp?fn:curryArgs.length?function(){return arguments.length?fn.apply(self,concat(curryArgs,arguments,0)):fn.apply(self,curryArgs)}:function(){return arguments.length?fn.apply(self,arguments):fn.call(self)}}function toJsonReplacer(key,value){var val=value;return"string"==typeof key&&"$"===key.charAt(0)&&"$"===key.charAt(1)?val=undefined:isWindow(value)?val="$WINDOW":value&&document===value?val="$DOCUMENT":isScope(value)&&(val="$SCOPE"),val}function toJson(obj,pretty){return"undefined"==typeof obj?undefined:(isNumber(pretty)||(pretty=pretty?2:null),JSON.stringify(obj,toJsonReplacer,pretty))}function fromJson(json){return isString(json)?JSON.parse(json):json}function timezoneToOffset(timezone,fallback){var requestedTimezoneOffset=Date.parse("Jan 01, 1970 00:00:00 "+timezone)/6e4;return isNaN(requestedTimezoneOffset)?fallback:requestedTimezoneOffset}function addDateMinutes(date,minutes){return date=new Date(date.getTime()),date.setMinutes(date.getMinutes()+minutes),date}function convertTimezoneToLocal(date,timezone,reverse){reverse=reverse?-1:1;var timezoneOffset=timezoneToOffset(timezone,date.getTimezoneOffset());return addDateMinutes(date,reverse*(timezoneOffset-date.getTimezoneOffset()))}function startingTag(element){element=jqLite(element).clone();try{element.empty()}catch(e){}var elemHtml=jqLite("
    ").append(element).html();try{return element[0].nodeType===NODE_TYPE_TEXT?lowercase(elemHtml):elemHtml.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(match,nodeName){return"<"+lowercase(nodeName)})}catch(e){return lowercase(elemHtml)}}function tryDecodeURIComponent(value){try{return decodeURIComponent(value)}catch(e){}}function parseKeyValue(keyValue){var obj={};return forEach((keyValue||"").split("&"),function(keyValue){var splitPoint,key,val;keyValue&&(key=keyValue=keyValue.replace(/\+/g,"%20"),splitPoint=keyValue.indexOf("="),-1!==splitPoint&&(key=keyValue.substring(0,splitPoint),val=keyValue.substring(splitPoint+1)),key=tryDecodeURIComponent(key),isDefined(key)&&(val=isDefined(val)?tryDecodeURIComponent(val):!0,hasOwnProperty.call(obj,key)?isArray(obj[key])?obj[key].push(val):obj[key]=[obj[key],val]:obj[key]=val))}),obj}function toKeyValue(obj){var parts=[];return forEach(obj,function(value,key){isArray(value)?forEach(value,function(arrayValue){parts.push(encodeUriQuery(key,!0)+(arrayValue===!0?"":"="+encodeUriQuery(arrayValue,!0)))}):parts.push(encodeUriQuery(key,!0)+(value===!0?"":"="+encodeUriQuery(value,!0)))}),parts.length?parts.join("&"):""}function encodeUriSegment(val){return encodeUriQuery(val,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function encodeUriQuery(val,pctEncodeSpaces){return encodeURIComponent(val).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,pctEncodeSpaces?"%20":"+")}function getNgAttribute(element,ngAttr){var attr,i,ii=ngAttrPrefixes.length;for(i=0;ii>i;++i)if(attr=ngAttrPrefixes[i]+ngAttr,isString(attr=element.getAttribute(attr)))return attr;return null}function angularInit(element,bootstrap){var appElement,module,config={};forEach(ngAttrPrefixes,function(prefix){var name=prefix+"app";!appElement&&element.hasAttribute&&element.hasAttribute(name)&&(appElement=element,module=element.getAttribute(name))}),forEach(ngAttrPrefixes,function(prefix){var candidate,name=prefix+"app";!appElement&&(candidate=element.querySelector("["+name.replace(":","\\:")+"]"))&&(appElement=candidate,module=candidate.getAttribute(name))}),appElement&&(config.strictDi=null!==getNgAttribute(appElement,"strict-di"),bootstrap(appElement,module?[module]:[],config))}function bootstrap(element,modules,config){isObject(config)||(config={});var defaultConfig={strictDi:!1};config=extend(defaultConfig,config);var doBootstrap=function(){if(element=jqLite(element),element.injector()){var tag=element[0]===document?"document":startingTag(element);throw ngMinErr("btstrpd","App Already Bootstrapped with this Element '{0}'",tag.replace(//,">"))}modules=modules||[],modules.unshift(["$provide",function($provide){$provide.value("$rootElement",element)}]),config.debugInfoEnabled&&modules.push(["$compileProvider",function($compileProvider){$compileProvider.debugInfoEnabled(!0)}]),modules.unshift("ng");var injector=createInjector(modules,config.strictDi);return injector.invoke(["$rootScope","$rootElement","$compile","$injector",function(scope,element,compile,injector){scope.$apply(function(){element.data("$injector",injector),compile(element)(scope)})}]),injector},NG_ENABLE_DEBUG_INFO=/^NG_ENABLE_DEBUG_INFO!/,NG_DEFER_BOOTSTRAP=/^NG_DEFER_BOOTSTRAP!/;return window&&NG_ENABLE_DEBUG_INFO.test(window.name)&&(config.debugInfoEnabled=!0,window.name=window.name.replace(NG_ENABLE_DEBUG_INFO,"")),window&&!NG_DEFER_BOOTSTRAP.test(window.name)?doBootstrap():(window.name=window.name.replace(NG_DEFER_BOOTSTRAP,""),angular.resumeBootstrap=function(extraModules){return forEach(extraModules,function(module){modules.push(module)}),doBootstrap()},void(isFunction(angular.resumeDeferredBootstrap)&&angular.resumeDeferredBootstrap()))}function reloadWithDebugInfo(){window.name="NG_ENABLE_DEBUG_INFO!"+window.name,window.location.reload()}function getTestability(rootElement){var injector=angular.element(rootElement).injector();if(!injector)throw ngMinErr("test","no injector found for element argument to getTestability");return injector.get("$$testability")}function snake_case(name,separator){return separator=separator||"_",name.replace(SNAKE_CASE_REGEXP,function(letter,pos){return(pos?separator:"")+letter.toLowerCase()})}function bindJQuery(){var originalCleanData;if(!bindJQueryFired){var jqName=jq();jQuery=window.jQuery,isDefined(jqName)&&(jQuery=null===jqName?undefined:window[jqName]),jQuery&&jQuery.fn.on?(jqLite=jQuery,extend(jQuery.fn,{scope:JQLitePrototype.scope,isolateScope:JQLitePrototype.isolateScope,controller:JQLitePrototype.controller,injector:JQLitePrototype.injector,inheritedData:JQLitePrototype.inheritedData}),originalCleanData=jQuery.cleanData,jQuery.cleanData=function(elems){var events;if(skipDestroyOnNextJQueryCleanData)skipDestroyOnNextJQueryCleanData=!1;else for(var elem,i=0;null!=(elem=elems[i]);i++)events=jQuery._data(elem,"events"),events&&events.$destroy&&jQuery(elem).triggerHandler("$destroy");originalCleanData(elems)}):jqLite=JQLite,angular.element=jqLite,bindJQueryFired=!0}}function assertArg(arg,name,reason){if(!arg)throw ngMinErr("areq","Argument '{0}' is {1}",name||"?",reason||"required");return arg}function assertArgFn(arg,name,acceptArrayAnnotation){return acceptArrayAnnotation&&isArray(arg)&&(arg=arg[arg.length-1]),assertArg(isFunction(arg),name,"not a function, got "+(arg&&"object"==typeof arg?arg.constructor.name||"Object":typeof arg)),arg}function assertNotHasOwnProperty(name,context){if("hasOwnProperty"===name)throw ngMinErr("badname","hasOwnProperty is not a valid {0} name",context)}function getter(obj,path,bindFnToScope){if(!path)return obj;for(var key,keys=path.split("."),lastInstance=obj,len=keys.length,i=0;len>i;i++)key=keys[i],obj&&(obj=(lastInstance=obj)[key]);return!bindFnToScope&&isFunction(obj)?bind(lastInstance,obj):obj}function getBlockNodes(nodes){var node=nodes[0],endNode=nodes[nodes.length-1],blockNodes=[node];do{if(node=node.nextSibling,!node)break;blockNodes.push(node)}while(node!==endNode);return jqLite(blockNodes)}function createMap(){return Object.create(null)}function setupModuleLoader(window){function ensure(obj,name,factory){return obj[name]||(obj[name]=factory())}var $injectorMinErr=minErr("$injector"),ngMinErr=minErr("ng"),angular=ensure(window,"angular",Object);return angular.$$minErr=angular.$$minErr||minErr,ensure(angular,"module",function(){var modules={};return function(name,requires,configFn){var assertNotHasOwnProperty=function(name,context){if("hasOwnProperty"===name)throw ngMinErr("badname","hasOwnProperty is not a valid {0} name",context)};return assertNotHasOwnProperty(name,"module"),requires&&modules.hasOwnProperty(name)&&(modules[name]=null),ensure(modules,name,function(){function invokeLater(provider,method,insertMethod,queue){return queue||(queue=invokeQueue),function(){return queue[insertMethod||"push"]([provider,method,arguments]),moduleInstance}}function invokeLaterAndSetModuleName(provider,method){return function(recipeName,factoryFunction){return factoryFunction&&isFunction(factoryFunction)&&(factoryFunction.$$moduleName=name),invokeQueue.push([provider,method,arguments]),moduleInstance}}if(!requires)throw $injectorMinErr("nomod","Module '{0}' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.",name);var invokeQueue=[],configBlocks=[],runBlocks=[],config=invokeLater("$injector","invoke","push",configBlocks),moduleInstance={_invokeQueue:invokeQueue,_configBlocks:configBlocks,_runBlocks:runBlocks,requires:requires,name:name,provider:invokeLaterAndSetModuleName("$provide","provider"),factory:invokeLaterAndSetModuleName("$provide","factory"),service:invokeLaterAndSetModuleName("$provide","service"),value:invokeLater("$provide","value"),constant:invokeLater("$provide","constant","unshift"),decorator:invokeLaterAndSetModuleName("$provide","decorator"),animation:invokeLaterAndSetModuleName("$animateProvider","register"),filter:invokeLaterAndSetModuleName("$filterProvider","register"),controller:invokeLaterAndSetModuleName("$controllerProvider","register"),directive:invokeLaterAndSetModuleName("$compileProvider","directive"),config:config,run:function(block){return runBlocks.push(block),this}};return configFn&&config(configFn),moduleInstance})}})}function serializeObject(obj){var seen=[];return JSON.stringify(obj,function(key,val){if(val=toJsonReplacer(key,val),isObject(val)){if(seen.indexOf(val)>=0)return"<>";seen.push(val)}return val})}function toDebugString(obj){return"function"==typeof obj?obj.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof obj?"undefined":"string"!=typeof obj?serializeObject(obj):obj}function publishExternalAPI(angular){extend(angular,{bootstrap:bootstrap,copy:copy,extend:extend,merge:merge,equals:equals,element:jqLite,forEach:forEach,injector:createInjector,noop:noop,bind:bind,toJson:toJson,fromJson:fromJson,identity:identity,isUndefined:isUndefined,isDefined:isDefined,isString:isString,isFunction:isFunction,isObject:isObject,isNumber:isNumber,isElement:isElement,isArray:isArray,version:version,isDate:isDate,lowercase:lowercase,uppercase:uppercase,callbacks:{counter:0},getTestability:getTestability,$$minErr:minErr,$$csp:csp,reloadWithDebugInfo:reloadWithDebugInfo}),(angularModule=setupModuleLoader(window))("ng",["ngLocale"],["$provide",function($provide){$provide.provider({$$sanitizeUri:$$SanitizeUriProvider}),$provide.provider("$compile",$CompileProvider).directive({a:htmlAnchorDirective,input:inputDirective,textarea:inputDirective,form:formDirective,script:scriptDirective,select:selectDirective,style:styleDirective,option:optionDirective,ngBind:ngBindDirective,ngBindHtml:ngBindHtmlDirective,ngBindTemplate:ngBindTemplateDirective,ngClass:ngClassDirective,ngClassEven:ngClassEvenDirective,ngClassOdd:ngClassOddDirective,ngCloak:ngCloakDirective,ngController:ngControllerDirective,ngForm:ngFormDirective,ngHide:ngHideDirective,ngIf:ngIfDirective,ngInclude:ngIncludeDirective,ngInit:ngInitDirective,ngNonBindable:ngNonBindableDirective,ngPluralize:ngPluralizeDirective,ngRepeat:ngRepeatDirective,ngShow:ngShowDirective,ngStyle:ngStyleDirective,ngSwitch:ngSwitchDirective,ngSwitchWhen:ngSwitchWhenDirective,ngSwitchDefault:ngSwitchDefaultDirective,ngOptions:ngOptionsDirective,ngTransclude:ngTranscludeDirective,ngModel:ngModelDirective,ngList:ngListDirective,ngChange:ngChangeDirective,pattern:patternDirective,ngPattern:patternDirective,required:requiredDirective,ngRequired:requiredDirective,minlength:minlengthDirective,ngMinlength:minlengthDirective,maxlength:maxlengthDirective,ngMaxlength:maxlengthDirective,ngValue:ngValueDirective,ngModelOptions:ngModelOptionsDirective}).directive({ngInclude:ngIncludeFillContentDirective}).directive(ngAttributeAliasDirectives).directive(ngEventDirectives),$provide.provider({$anchorScroll:$AnchorScrollProvider,$animate:$AnimateProvider,$animateCss:$CoreAnimateCssProvider,$$animateQueue:$$CoreAnimateQueueProvider,$$AnimateRunner:$$CoreAnimateRunnerProvider,$browser:$BrowserProvider,$cacheFactory:$CacheFactoryProvider,$controller:$ControllerProvider,$document:$DocumentProvider,$exceptionHandler:$ExceptionHandlerProvider,$filter:$FilterProvider,$$forceReflow:$$ForceReflowProvider,$interpolate:$InterpolateProvider,$interval:$IntervalProvider,$http:$HttpProvider,$httpParamSerializer:$HttpParamSerializerProvider,$httpParamSerializerJQLike:$HttpParamSerializerJQLikeProvider,$httpBackend:$HttpBackendProvider,$location:$LocationProvider,$log:$LogProvider,$parse:$ParseProvider,$rootScope:$RootScopeProvider,$q:$QProvider,$$q:$$QProvider,$sce:$SceProvider,$sceDelegate:$SceDelegateProvider,$sniffer:$SnifferProvider,$templateCache:$TemplateCacheProvider,$templateRequest:$TemplateRequestProvider,$$testability:$$TestabilityProvider,$timeout:$TimeoutProvider,$window:$WindowProvider,$$rAF:$$RAFProvider,$$jqLite:$$jqLiteProvider,$$HashMap:$$HashMapProvider,$$cookieReader:$$CookieReaderProvider})}])}function jqNextId(){return++jqId}function camelCase(name){return name.replace(SPECIAL_CHARS_REGEXP,function(_,separator,letter,offset){return offset?letter.toUpperCase():letter}).replace(MOZ_HACK_REGEXP,"Moz$1")}function jqLiteIsTextNode(html){return!HTML_REGEXP.test(html)}function jqLiteAcceptsData(node){var nodeType=node.nodeType;return nodeType===NODE_TYPE_ELEMENT||!nodeType||nodeType===NODE_TYPE_DOCUMENT}function jqLiteHasData(node){for(var key in jqCache[node.ng339])return!0;return!1}function jqLiteBuildFragment(html,context){var tmp,tag,wrap,i,fragment=context.createDocumentFragment(),nodes=[];if(jqLiteIsTextNode(html))nodes.push(context.createTextNode(html));else{for(tmp=tmp||fragment.appendChild(context.createElement("div")),tag=(TAG_NAME_REGEXP.exec(html)||["",""])[1].toLowerCase(),wrap=wrapMap[tag]||wrapMap._default,tmp.innerHTML=wrap[1]+html.replace(XHTML_TAG_REGEXP,"<$1>")+wrap[2],i=wrap[0];i--;)tmp=tmp.lastChild;nodes=concat(nodes,tmp.childNodes),tmp=fragment.firstChild,tmp.textContent=""}return fragment.textContent="",fragment.innerHTML="",forEach(nodes,function(node){fragment.appendChild(node)}),fragment}function jqLiteParseHTML(html,context){context=context||document;var parsed;return(parsed=SINGLE_TAG_REGEXP.exec(html))?[context.createElement(parsed[1])]:(parsed=jqLiteBuildFragment(html,context))?parsed.childNodes:[]}function JQLite(element){if(element instanceof JQLite)return element;var argIsString;if(isString(element)&&(element=trim(element),argIsString=!0),!(this instanceof JQLite)){if(argIsString&&"<"!=element.charAt(0))throw jqLiteMinErr("nosel","Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element");return new JQLite(element)}argIsString?jqLiteAddNodes(this,jqLiteParseHTML(element)):jqLiteAddNodes(this,element)}function jqLiteClone(element){return element.cloneNode(!0)}function jqLiteDealoc(element,onlyDescendants){if(onlyDescendants||jqLiteRemoveData(element),element.querySelectorAll)for(var descendants=element.querySelectorAll("*"),i=0,l=descendants.length;l>i;i++)jqLiteRemoveData(descendants[i])}function jqLiteOff(element,type,fn,unsupported){if(isDefined(unsupported))throw jqLiteMinErr("offargs","jqLite#off() does not support the `selector` argument");var expandoStore=jqLiteExpandoStore(element),events=expandoStore&&expandoStore.events,handle=expandoStore&&expandoStore.handle;if(handle)if(type)forEach(type.split(" "),function(type){if(isDefined(fn)){var listenerFns=events[type];if(arrayRemove(listenerFns||[],fn),listenerFns&&listenerFns.length>0)return}removeEventListenerFn(element,type,handle),delete events[type]});else for(type in events)"$destroy"!==type&&removeEventListenerFn(element,type,handle),delete events[type]}function jqLiteRemoveData(element,name){var expandoId=element.ng339,expandoStore=expandoId&&jqCache[expandoId];if(expandoStore){if(name)return void delete expandoStore.data[name];expandoStore.handle&&(expandoStore.events.$destroy&&expandoStore.handle({},"$destroy"),jqLiteOff(element)),delete jqCache[expandoId],element.ng339=undefined}}function jqLiteExpandoStore(element,createIfNecessary){var expandoId=element.ng339,expandoStore=expandoId&&jqCache[expandoId];return createIfNecessary&&!expandoStore&&(element.ng339=expandoId=jqNextId(),expandoStore=jqCache[expandoId]={events:{},data:{},handle:undefined}),expandoStore}function jqLiteData(element,key,value){if(jqLiteAcceptsData(element)){var isSimpleSetter=isDefined(value),isSimpleGetter=!isSimpleSetter&&key&&!isObject(key),massGetter=!key,expandoStore=jqLiteExpandoStore(element,!isSimpleGetter),data=expandoStore&&expandoStore.data;if(isSimpleSetter)data[key]=value;else{if(massGetter)return data;if(isSimpleGetter)return data&&data[key];extend(data,key)}}}function jqLiteHasClass(element,selector){return element.getAttribute?(" "+(element.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+selector+" ")>-1:!1}function jqLiteRemoveClass(element,cssClasses){cssClasses&&element.setAttribute&&forEach(cssClasses.split(" "),function(cssClass){element.setAttribute("class",trim((" "+(element.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+trim(cssClass)+" "," ")))})}function jqLiteAddClass(element,cssClasses){if(cssClasses&&element.setAttribute){var existingClasses=(" "+(element.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");forEach(cssClasses.split(" "),function(cssClass){cssClass=trim(cssClass),-1===existingClasses.indexOf(" "+cssClass+" ")&&(existingClasses+=cssClass+" ")}),element.setAttribute("class",trim(existingClasses))}}function jqLiteAddNodes(root,elements){if(elements)if(elements.nodeType)root[root.length++]=elements;else{var length=elements.length;if("number"==typeof length&&elements.window!==elements){if(length)for(var i=0;length>i;i++)root[root.length++]=elements[i]}else root[root.length++]=elements}}function jqLiteController(element,name){return jqLiteInheritedData(element,"$"+(name||"ngController")+"Controller")}function jqLiteInheritedData(element,name,value){element.nodeType==NODE_TYPE_DOCUMENT&&(element=element.documentElement);for(var names=isArray(name)?name:[name];element;){for(var i=0,ii=names.length;ii>i;i++)if((value=jqLite.data(element,names[i]))!==undefined)return value;element=element.parentNode||element.nodeType===NODE_TYPE_DOCUMENT_FRAGMENT&&element.host}}function jqLiteEmpty(element){for(jqLiteDealoc(element,!0);element.firstChild;)element.removeChild(element.firstChild)}function jqLiteRemove(element,keepData){keepData||jqLiteDealoc(element);var parent=element.parentNode;parent&&parent.removeChild(element)}function jqLiteDocumentLoaded(action,win){win=win||window,"complete"===win.document.readyState?win.setTimeout(action):jqLite(win).on("load",action)}function getBooleanAttrName(element,name){var booleanAttr=BOOLEAN_ATTR[name.toLowerCase()];return booleanAttr&&BOOLEAN_ELEMENTS[nodeName_(element)]&&booleanAttr}function getAliasedAttrName(element,name){var nodeName=element.nodeName;return("INPUT"===nodeName||"TEXTAREA"===nodeName)&&ALIASED_ATTR[name]}function createEventHandler(element,events){var eventHandler=function(event,type){event.isDefaultPrevented=function(){return event.defaultPrevented};var eventFns=events[type||event.type],eventFnsLength=eventFns?eventFns.length:0;if(eventFnsLength){if(isUndefined(event.immediatePropagationStopped)){var originalStopImmediatePropagation=event.stopImmediatePropagation;event.stopImmediatePropagation=function(){event.immediatePropagationStopped=!0,event.stopPropagation&&event.stopPropagation(),originalStopImmediatePropagation&&originalStopImmediatePropagation.call(event)}}event.isImmediatePropagationStopped=function(){return event.immediatePropagationStopped===!0},eventFnsLength>1&&(eventFns=shallowCopy(eventFns));for(var i=0;eventFnsLength>i;i++)event.isImmediatePropagationStopped()||eventFns[i].call(element,event)}};return eventHandler.elem=element,eventHandler}function $$jqLiteProvider(){this.$get=function(){return extend(JQLite,{hasClass:function(node,classes){return node.attr&&(node=node[0]),jqLiteHasClass(node,classes)},addClass:function(node,classes){return node.attr&&(node=node[0]),jqLiteAddClass(node,classes)},removeClass:function(node,classes){return node.attr&&(node=node[0]),jqLiteRemoveClass(node,classes)}})}}function hashKey(obj,nextUidFn){var key=obj&&obj.$$hashKey;if(key)return"function"==typeof key&&(key=obj.$$hashKey()),key;var objType=typeof obj;return key="function"==objType||"object"==objType&&null!==obj?obj.$$hashKey=objType+":"+(nextUidFn||nextUid)():objType+":"+obj}function HashMap(array,isolatedUid){if(isolatedUid){var uid=0;this.nextUid=function(){return++uid}}forEach(array,this.put,this)}function anonFn(fn){var fnText=fn.toString().replace(STRIP_COMMENTS,""),args=fnText.match(FN_ARGS);return args?"function("+(args[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function annotate(fn,strictDi,name){var $inject,fnText,argDecl,last;if("function"==typeof fn){if(!($inject=fn.$inject)){if($inject=[],fn.length){if(strictDi)throw isString(name)&&name||(name=fn.name||anonFn(fn)),$injectorMinErr("strictdi","{0} is not using explicit annotation and cannot be invoked in strict mode",name);fnText=fn.toString().replace(STRIP_COMMENTS,""),argDecl=fnText.match(FN_ARGS),forEach(argDecl[1].split(FN_ARG_SPLIT),function(arg){arg.replace(FN_ARG,function(all,underscore,name){$inject.push(name)})})}fn.$inject=$inject}}else isArray(fn)?(last=fn.length-1,assertArgFn(fn[last],"fn"),$inject=fn.slice(0,last)):assertArgFn(fn,"fn",!0);return $inject}function createInjector(modulesToLoad,strictDi){function supportObject(delegate){return function(key,value){return isObject(key)?void forEach(key,reverseParams(delegate)):delegate(key,value)}}function provider(name,provider_){if(assertNotHasOwnProperty(name,"service"),(isFunction(provider_)||isArray(provider_))&&(provider_=providerInjector.instantiate(provider_)),!provider_.$get)throw $injectorMinErr("pget","Provider '{0}' must define $get factory method.",name);return providerCache[name+providerSuffix]=provider_}function enforceReturnValue(name,factory){return function(){var result=instanceInjector.invoke(factory,this);if(isUndefined(result))throw $injectorMinErr("undef","Provider '{0}' must return a value from $get factory method.",name);return result}}function factory(name,factoryFn,enforce){return provider(name,{$get:enforce!==!1?enforceReturnValue(name,factoryFn):factoryFn})}function service(name,constructor){return factory(name,["$injector",function($injector){return $injector.instantiate(constructor)}])}function value(name,val){return factory(name,valueFn(val),!1)}function constant(name,value){assertNotHasOwnProperty(name,"constant"),providerCache[name]=value,instanceCache[name]=value}function decorator(serviceName,decorFn){var origProvider=providerInjector.get(serviceName+providerSuffix),orig$get=origProvider.$get;origProvider.$get=function(){var origInstance=instanceInjector.invoke(orig$get,origProvider);return instanceInjector.invoke(decorFn,null,{$delegate:origInstance})}}function loadModules(modulesToLoad){assertArg(isUndefined(modulesToLoad)||isArray(modulesToLoad),"modulesToLoad","not an array");var moduleFn,runBlocks=[];return forEach(modulesToLoad,function(module){function runInvokeQueue(queue){var i,ii;for(i=0,ii=queue.length;ii>i;i++){var invokeArgs=queue[i],provider=providerInjector.get(invokeArgs[0]);provider[invokeArgs[1]].apply(provider,invokeArgs[2])}}if(!loadedModules.get(module)){loadedModules.put(module,!0);try{isString(module)?(moduleFn=angularModule(module),runBlocks=runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks),runInvokeQueue(moduleFn._invokeQueue),runInvokeQueue(moduleFn._configBlocks)):isFunction(module)?runBlocks.push(providerInjector.invoke(module)):isArray(module)?runBlocks.push(providerInjector.invoke(module)):assertArgFn(module,"module"); +}catch(e){throw isArray(module)&&(module=module[module.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),$injectorMinErr("modulerr","Failed to instantiate module {0} due to:\n{1}",module,e.stack||e.message||e)}}}),runBlocks}function createInternalInjector(cache,factory){function getService(serviceName,caller){if(cache.hasOwnProperty(serviceName)){if(cache[serviceName]===INSTANTIATING)throw $injectorMinErr("cdep","Circular dependency found: {0}",serviceName+" <- "+path.join(" <- "));return cache[serviceName]}try{return path.unshift(serviceName),cache[serviceName]=INSTANTIATING,cache[serviceName]=factory(serviceName,caller)}catch(err){throw cache[serviceName]===INSTANTIATING&&delete cache[serviceName],err}finally{path.shift()}}function invoke(fn,self,locals,serviceName){"string"==typeof locals&&(serviceName=locals,locals=null);var length,i,key,args=[],$inject=createInjector.$$annotate(fn,strictDi,serviceName);for(i=0,length=$inject.length;length>i;i++){if(key=$inject[i],"string"!=typeof key)throw $injectorMinErr("itkn","Incorrect injection token! Expected service name as string, got {0}",key);args.push(locals&&locals.hasOwnProperty(key)?locals[key]:getService(key,serviceName))}return isArray(fn)&&(fn=fn[length]),fn.apply(self,args)}function instantiate(Type,locals,serviceName){var instance=Object.create((isArray(Type)?Type[Type.length-1]:Type).prototype||null),returnedValue=invoke(Type,instance,locals,serviceName);return isObject(returnedValue)||isFunction(returnedValue)?returnedValue:instance}return{invoke:invoke,instantiate:instantiate,get:getService,annotate:createInjector.$$annotate,has:function(name){return providerCache.hasOwnProperty(name+providerSuffix)||cache.hasOwnProperty(name)}}}strictDi=strictDi===!0;var INSTANTIATING={},providerSuffix="Provider",path=[],loadedModules=new HashMap([],!0),providerCache={$provide:{provider:supportObject(provider),factory:supportObject(factory),service:supportObject(service),value:supportObject(value),constant:supportObject(constant),decorator:decorator}},providerInjector=providerCache.$injector=createInternalInjector(providerCache,function(serviceName,caller){throw angular.isString(caller)&&path.push(caller),$injectorMinErr("unpr","Unknown provider: {0}",path.join(" <- "))}),instanceCache={},instanceInjector=instanceCache.$injector=createInternalInjector(instanceCache,function(serviceName,caller){var provider=providerInjector.get(serviceName+providerSuffix,caller);return instanceInjector.invoke(provider.$get,provider,undefined,serviceName)});return forEach(loadModules(modulesToLoad),function(fn){fn&&instanceInjector.invoke(fn)}),instanceInjector}function $AnchorScrollProvider(){var autoScrollingEnabled=!0;this.disableAutoScrolling=function(){autoScrollingEnabled=!1},this.$get=["$window","$location","$rootScope",function($window,$location,$rootScope){function getFirstAnchor(list){var result=null;return Array.prototype.some.call(list,function(element){return"a"===nodeName_(element)?(result=element,!0):void 0}),result}function getYOffset(){var offset=scroll.yOffset;if(isFunction(offset))offset=offset();else if(isElement(offset)){var elem=offset[0],style=$window.getComputedStyle(elem);offset="fixed"!==style.position?0:elem.getBoundingClientRect().bottom}else isNumber(offset)||(offset=0);return offset}function scrollTo(elem){if(elem){elem.scrollIntoView();var offset=getYOffset();if(offset){var elemTop=elem.getBoundingClientRect().top;$window.scrollBy(0,elemTop-offset)}}else $window.scrollTo(0,0)}function scroll(hash){hash=isString(hash)?hash:$location.hash();var elm;hash?(elm=document.getElementById(hash))?scrollTo(elm):(elm=getFirstAnchor(document.getElementsByName(hash)))?scrollTo(elm):"top"===hash&&scrollTo(null):scrollTo(null)}var document=$window.document;return autoScrollingEnabled&&$rootScope.$watch(function(){return $location.hash()},function(newVal,oldVal){(newVal!==oldVal||""!==newVal)&&jqLiteDocumentLoaded(function(){$rootScope.$evalAsync(scroll)})}),scroll}]}function mergeClasses(a,b){return a||b?a?b?(isArray(a)&&(a=a.join(" ")),isArray(b)&&(b=b.join(" ")),a+" "+b):a:b:""}function extractElementNode(element){for(var i=0;icapacity&&this.remove(staleEnd.key),value}},get:function(key){if(capacity").parent()[0])});var compositeLinkFn=compileNodes($compileNodes,transcludeFn,$compileNodes,maxPriority,ignoreDirective,previousCompileContext);compile.$$addScopeClass($compileNodes);var namespace=null;return function(scope,cloneConnectFn,options){assertArg(scope,"scope"),options=options||{};var parentBoundTranscludeFn=options.parentBoundTranscludeFn,transcludeControllers=options.transcludeControllers,futureParentElement=options.futureParentElement;parentBoundTranscludeFn&&parentBoundTranscludeFn.$$boundTransclude&&(parentBoundTranscludeFn=parentBoundTranscludeFn.$$boundTransclude),namespace||(namespace=detectNamespaceForChildElements(futureParentElement));var $linkNode;if($linkNode="html"!==namespace?jqLite(wrapTemplate(namespace,jqLite("
    ").append($compileNodes).html())):cloneConnectFn?JQLitePrototype.clone.call($compileNodes):$compileNodes,transcludeControllers)for(var controllerName in transcludeControllers)$linkNode.data("$"+controllerName+"Controller",transcludeControllers[controllerName].instance);return compile.$$addScopeInfo($linkNode,scope),cloneConnectFn&&cloneConnectFn($linkNode,scope),compositeLinkFn&&compositeLinkFn(scope,$linkNode,$linkNode,parentBoundTranscludeFn),$linkNode}}function detectNamespaceForChildElements(parentElement){var node=parentElement&&parentElement[0];return node&&"foreignobject"!==nodeName_(node)&&node.toString().match(/SVG/)?"svg":"html"}function compileNodes(nodeList,transcludeFn,$rootElement,maxPriority,ignoreDirective,previousCompileContext){function compositeLinkFn(scope,nodeList,$rootElement,parentBoundTranscludeFn){var nodeLinkFn,childLinkFn,node,childScope,i,ii,idx,childBoundTranscludeFn,stableNodeList;if(nodeLinkFnFound){var nodeListLength=nodeList.length;for(stableNodeList=new Array(nodeListLength),i=0;ii;)if(node=stableNodeList[linkFns[i++]],nodeLinkFn=linkFns[i++],childLinkFn=linkFns[i++],nodeLinkFn){if(nodeLinkFn.scope){childScope=scope.$new(),compile.$$addScopeInfo(jqLite(node),childScope);var destroyBindings=nodeLinkFn.$$destroyBindings;destroyBindings&&(nodeLinkFn.$$destroyBindings=null,childScope.$on("$destroyed",destroyBindings))}else childScope=scope;childBoundTranscludeFn=nodeLinkFn.transcludeOnThisElement?createBoundTranscludeFn(scope,nodeLinkFn.transclude,parentBoundTranscludeFn):!nodeLinkFn.templateOnThisElement&&parentBoundTranscludeFn?parentBoundTranscludeFn:!parentBoundTranscludeFn&&transcludeFn?createBoundTranscludeFn(scope,transcludeFn):null,nodeLinkFn(childLinkFn,childScope,node,$rootElement,childBoundTranscludeFn,nodeLinkFn)}else childLinkFn&&childLinkFn(scope,node.childNodes,undefined,parentBoundTranscludeFn)}for(var attrs,directives,nodeLinkFn,childNodes,childLinkFn,linkFnFound,nodeLinkFnFound,linkFns=[],i=0;ij;j++){var attrStartName=!1,attrEndName=!1;attr=nAttrs[j],name=attr.name,value=trim(attr.value),ngAttrName=directiveNormalize(name),(isNgAttr=NG_ATTR_BINDING.test(ngAttrName))&&(name=name.replace(PREFIX_REGEXP,"").substr(8).replace(/_(.)/g,function(match,letter){return letter.toUpperCase()}));var directiveNName=ngAttrName.replace(/(Start|End)$/,"");directiveIsMultiElement(directiveNName)&&ngAttrName===directiveNName+"Start"&&(attrStartName=name,attrEndName=name.substr(0,name.length-5)+"end",name=name.substr(0,name.length-6)),nName=directiveNormalize(name.toLowerCase()),attrsMap[nName]=name,(isNgAttr||!attrs.hasOwnProperty(nName))&&(attrs[nName]=value,getBooleanAttrName(node,nName)&&(attrs[nName]=!0)),addAttrInterpolateDirective(node,directives,value,nName,isNgAttr),addDirective(directives,nName,"A",maxPriority,ignoreDirective,attrStartName,attrEndName)}if(className=node.className,isObject(className)&&(className=className.animVal),isString(className)&&""!==className)for(;match=CLASS_DIRECTIVE_REGEXP.exec(className);)nName=directiveNormalize(match[2]),addDirective(directives,nName,"C",maxPriority,ignoreDirective)&&(attrs[nName]=trim(match[3])),className=className.substr(match.index+match[0].length);break;case NODE_TYPE_TEXT:if(11===msie)for(;node.parentNode&&node.nextSibling&&node.nextSibling.nodeType===NODE_TYPE_TEXT;)node.nodeValue=node.nodeValue+node.nextSibling.nodeValue,node.parentNode.removeChild(node.nextSibling);addTextInterpolateDirective(directives,node.nodeValue);break;case NODE_TYPE_COMMENT:try{match=COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue),match&&(nName=directiveNormalize(match[1]),addDirective(directives,nName,"M",maxPriority,ignoreDirective)&&(attrs[nName]=trim(match[2])))}catch(e){}}return directives.sort(byPriority),directives}function groupScan(node,attrStart,attrEnd){var nodes=[],depth=0;if(attrStart&&node.hasAttribute&&node.hasAttribute(attrStart)){do{if(!node)throw $compileMinErr("uterdir","Unterminated attribute, found '{0}' but no matching '{1}' found.",attrStart,attrEnd);node.nodeType==NODE_TYPE_ELEMENT&&(node.hasAttribute(attrStart)&&depth++,node.hasAttribute(attrEnd)&&depth--),nodes.push(node),node=node.nextSibling}while(depth>0)}else nodes.push(node);return jqLite(nodes)}function groupElementsLinkFnWrapper(linkFn,attrStart,attrEnd){return function(scope,element,attrs,controllers,transcludeFn){return element=groupScan(element[0],attrStart,attrEnd),linkFn(scope,element,attrs,controllers,transcludeFn)}}function applyDirectivesToNode(directives,compileNode,templateAttrs,transcludeFn,jqCollection,originalReplaceDirective,preLinkFns,postLinkFns,previousCompileContext){function addLinkFns(pre,post,attrStart,attrEnd){pre&&(attrStart&&(pre=groupElementsLinkFnWrapper(pre,attrStart,attrEnd)),pre.require=directive.require,pre.directiveName=directiveName,(newIsolateScopeDirective===directive||directive.$$isolateScope)&&(pre=cloneAndAnnotateFn(pre,{isolateScope:!0})),preLinkFns.push(pre)),post&&(attrStart&&(post=groupElementsLinkFnWrapper(post,attrStart,attrEnd)),post.require=directive.require,post.directiveName=directiveName,(newIsolateScopeDirective===directive||directive.$$isolateScope)&&(post=cloneAndAnnotateFn(post,{isolateScope:!0})),postLinkFns.push(post))}function getControllers(directiveName,require,$element,elementControllers){var value;if(isString(require)){var match=require.match(REQUIRE_PREFIX_REGEXP),name=require.substring(match[0].length),inheritType=match[1]||match[3],optional="?"===match[2];if("^^"===inheritType?$element=$element.parent():(value=elementControllers&&elementControllers[name],value=value&&value.instance),!value){var dataName="$"+name+"Controller";value=inheritType?$element.inheritedData(dataName):$element.data(dataName)}if(!value&&!optional)throw $compileMinErr("ctreq","Controller '{0}', required by directive '{1}', can't be found!",name,directiveName)}else if(isArray(require)){value=[];for(var i=0,ii=require.length;ii>i;i++)value[i]=getControllers(directiveName,require[i],$element,elementControllers)}return value||null}function setupControllers($element,attrs,transcludeFn,controllerDirectives,isolateScope,scope){var elementControllers=createMap();for(var controllerKey in controllerDirectives){var directive=controllerDirectives[controllerKey],locals={$scope:directive===newIsolateScopeDirective||directive.$$isolateScope?isolateScope:scope,$element:$element,$attrs:attrs,$transclude:transcludeFn},controller=directive.controller;"@"==controller&&(controller=attrs[directive.name]);var controllerInstance=$controller(controller,locals,!0,directive.controllerAs);elementControllers[directive.name]=controllerInstance,hasElementTranscludeDirective||$element.data("$"+directive.name+"Controller",controllerInstance.instance)}return elementControllers}function nodeLinkFn(childLinkFn,scope,linkNode,$rootElement,boundTranscludeFn,thisLinkFn){function controllersBoundTransclude(scope,cloneAttachFn,futureParentElement){var transcludeControllers;return isScope(scope)||(futureParentElement=cloneAttachFn,cloneAttachFn=scope,scope=undefined),hasElementTranscludeDirective&&(transcludeControllers=elementControllers),futureParentElement||(futureParentElement=hasElementTranscludeDirective?$element.parent():$element),boundTranscludeFn(scope,cloneAttachFn,transcludeControllers,futureParentElement,scopeToChild)}var i,ii,linkFn,controller,isolateScope,elementControllers,transcludeFn,$element,attrs;if(compileNode===linkNode?(attrs=templateAttrs,$element=templateAttrs.$$element):($element=jqLite(linkNode),attrs=new Attributes($element,templateAttrs)),newIsolateScopeDirective&&(isolateScope=scope.$new(!0)),boundTranscludeFn&&(transcludeFn=controllersBoundTransclude,transcludeFn.$$boundTransclude=boundTranscludeFn),controllerDirectives&&(elementControllers=setupControllers($element,attrs,transcludeFn,controllerDirectives,isolateScope,scope)),newIsolateScopeDirective&&(compile.$$addScopeInfo($element,isolateScope,!0,!(templateDirective&&(templateDirective===newIsolateScopeDirective||templateDirective===newIsolateScopeDirective.$$originalDirective))),compile.$$addScopeClass($element,!0),isolateScope.$$isolateBindings=newIsolateScopeDirective.$$isolateBindings,initializeDirectiveBindings(scope,attrs,isolateScope,isolateScope.$$isolateBindings,newIsolateScopeDirective,isolateScope)),elementControllers){var bindings,controllerForBindings,scopeDirective=newIsolateScopeDirective||newScopeDirective;scopeDirective&&elementControllers[scopeDirective.name]&&(bindings=scopeDirective.$$bindings.bindToController,controller=elementControllers[scopeDirective.name],controller&&controller.identifier&&bindings&&(controllerForBindings=controller,thisLinkFn.$$destroyBindings=initializeDirectiveBindings(scope,attrs,controller.instance,bindings,scopeDirective)));for(i in elementControllers){controller=elementControllers[i];var controllerResult=controller();controllerResult!==controller.instance&&(controller.instance=controllerResult,$element.data("$"+i+"Controller",controllerResult),controller===controllerForBindings&&(thisLinkFn.$$destroyBindings(),thisLinkFn.$$destroyBindings=initializeDirectiveBindings(scope,attrs,controllerResult,bindings,scopeDirective)))}}for(i=0,ii=preLinkFns.length;ii>i;i++)linkFn=preLinkFns[i],invokeLinkFn(linkFn,linkFn.isolateScope?isolateScope:scope,$element,attrs,linkFn.require&&getControllers(linkFn.directiveName,linkFn.require,$element,elementControllers),transcludeFn);var scopeToChild=scope;for(newIsolateScopeDirective&&(newIsolateScopeDirective.template||null===newIsolateScopeDirective.templateUrl)&&(scopeToChild=isolateScope),childLinkFn&&childLinkFn(scopeToChild,linkNode.childNodes,undefined,boundTranscludeFn),i=postLinkFns.length-1;i>=0;i--)linkFn=postLinkFns[i],invokeLinkFn(linkFn,linkFn.isolateScope?isolateScope:scope,$element,attrs,linkFn.require&&getControllers(linkFn.directiveName,linkFn.require,$element,elementControllers),transcludeFn)}previousCompileContext=previousCompileContext||{};for(var directive,directiveName,$template,linkFn,directiveValue,terminalPriority=-Number.MAX_VALUE,newScopeDirective=previousCompileContext.newScopeDirective,controllerDirectives=previousCompileContext.controllerDirectives,newIsolateScopeDirective=previousCompileContext.newIsolateScopeDirective,templateDirective=previousCompileContext.templateDirective,nonTlbTranscludeDirective=previousCompileContext.nonTlbTranscludeDirective,hasTranscludeDirective=!1,hasTemplate=!1,hasElementTranscludeDirective=previousCompileContext.hasElementTranscludeDirective,$compileNode=templateAttrs.$$element=jqLite(compileNode),replaceDirective=originalReplaceDirective,childTranscludeFn=transcludeFn,i=0,ii=directives.length;ii>i;i++){directive=directives[i];var attrStart=directive.$$start,attrEnd=directive.$$end;if(attrStart&&($compileNode=groupScan(compileNode,attrStart,attrEnd)),$template=undefined,terminalPriority>directive.priority)break;if((directiveValue=directive.scope)&&(directive.templateUrl||(isObject(directiveValue)?(assertNoDuplicate("new/isolated scope",newIsolateScopeDirective||newScopeDirective,directive,$compileNode),newIsolateScopeDirective=directive):assertNoDuplicate("new/isolated scope",newIsolateScopeDirective,directive,$compileNode)),newScopeDirective=newScopeDirective||directive),directiveName=directive.name,!directive.templateUrl&&directive.controller&&(directiveValue=directive.controller,controllerDirectives=controllerDirectives||createMap(),assertNoDuplicate("'"+directiveName+"' controller",controllerDirectives[directiveName],directive,$compileNode),controllerDirectives[directiveName]=directive),(directiveValue=directive.transclude)&&(hasTranscludeDirective=!0,directive.$$tlb||(assertNoDuplicate("transclusion",nonTlbTranscludeDirective,directive,$compileNode),nonTlbTranscludeDirective=directive),"element"==directiveValue?(hasElementTranscludeDirective=!0,terminalPriority=directive.priority,$template=$compileNode,$compileNode=templateAttrs.$$element=jqLite(document.createComment(" "+directiveName+": "+templateAttrs[directiveName]+" ")),compileNode=$compileNode[0],replaceWith(jqCollection,sliceArgs($template),compileNode),childTranscludeFn=compile($template,transcludeFn,terminalPriority,replaceDirective&&replaceDirective.name,{nonTlbTranscludeDirective:nonTlbTranscludeDirective})):($template=jqLite(jqLiteClone(compileNode)).contents(),$compileNode.empty(),childTranscludeFn=compile($template,transcludeFn))),directive.template)if(hasTemplate=!0,assertNoDuplicate("template",templateDirective,directive,$compileNode),templateDirective=directive,directiveValue=isFunction(directive.template)?directive.template($compileNode,templateAttrs):directive.template,directiveValue=denormalizeTemplate(directiveValue),directive.replace){if(replaceDirective=directive,$template=jqLiteIsTextNode(directiveValue)?[]:removeComments(wrapTemplate(directive.templateNamespace,trim(directiveValue))),compileNode=$template[0],1!=$template.length||compileNode.nodeType!==NODE_TYPE_ELEMENT)throw $compileMinErr("tplrt","Template for directive '{0}' must have exactly one root element. {1}",directiveName,"");replaceWith(jqCollection,$compileNode,compileNode);var newTemplateAttrs={$attr:{}},templateDirectives=collectDirectives(compileNode,[],newTemplateAttrs),unprocessedDirectives=directives.splice(i+1,directives.length-(i+1));newIsolateScopeDirective&&markDirectivesAsIsolate(templateDirectives),directives=directives.concat(templateDirectives).concat(unprocessedDirectives),mergeTemplateAttributes(templateAttrs,newTemplateAttrs),ii=directives.length}else $compileNode.html(directiveValue);if(directive.templateUrl)hasTemplate=!0,assertNoDuplicate("template",templateDirective,directive,$compileNode),templateDirective=directive,directive.replace&&(replaceDirective=directive),nodeLinkFn=compileTemplateUrl(directives.splice(i,directives.length-i),$compileNode,templateAttrs,jqCollection,hasTranscludeDirective&&childTranscludeFn,preLinkFns,postLinkFns,{controllerDirectives:controllerDirectives,newScopeDirective:newScopeDirective!==directive&&newScopeDirective,newIsolateScopeDirective:newIsolateScopeDirective,templateDirective:templateDirective,nonTlbTranscludeDirective:nonTlbTranscludeDirective}),ii=directives.length;else if(directive.compile)try{linkFn=directive.compile($compileNode,templateAttrs,childTranscludeFn),isFunction(linkFn)?addLinkFns(null,linkFn,attrStart,attrEnd):linkFn&&addLinkFns(linkFn.pre,linkFn.post,attrStart,attrEnd)}catch(e){$exceptionHandler(e,startingTag($compileNode))}directive.terminal&&(nodeLinkFn.terminal=!0,terminalPriority=Math.max(terminalPriority,directive.priority))}return nodeLinkFn.scope=newScopeDirective&&newScopeDirective.scope===!0,nodeLinkFn.transcludeOnThisElement=hasTranscludeDirective,nodeLinkFn.templateOnThisElement=hasTemplate,nodeLinkFn.transclude=childTranscludeFn,previousCompileContext.hasElementTranscludeDirective=hasElementTranscludeDirective, +nodeLinkFn}function markDirectivesAsIsolate(directives){for(var j=0,jj=directives.length;jj>j;j++)directives[j]=inherit(directives[j],{$$isolateScope:!0})}function addDirective(tDirectives,name,location,maxPriority,ignoreDirective,startAttrName,endAttrName){if(name===ignoreDirective)return null;var match=null;if(hasDirectives.hasOwnProperty(name))for(var directive,directives=$injector.get(name+Suffix),i=0,ii=directives.length;ii>i;i++)try{directive=directives[i],(maxPriority===undefined||maxPriority>directive.priority)&&-1!=directive.restrict.indexOf(location)&&(startAttrName&&(directive=inherit(directive,{$$start:startAttrName,$$end:endAttrName})),tDirectives.push(directive),match=directive)}catch(e){$exceptionHandler(e)}return match}function directiveIsMultiElement(name){if(hasDirectives.hasOwnProperty(name))for(var directive,directives=$injector.get(name+Suffix),i=0,ii=directives.length;ii>i;i++)if(directive=directives[i],directive.multiElement)return!0;return!1}function mergeTemplateAttributes(dst,src){var srcAttr=src.$attr,dstAttr=dst.$attr,$element=dst.$$element;forEach(dst,function(value,key){"$"!=key.charAt(0)&&(src[key]&&src[key]!==value&&(value+=("style"===key?";":" ")+src[key]),dst.$set(key,value,!0,srcAttr[key]))}),forEach(src,function(value,key){"class"==key?(safeAddClass($element,value),dst["class"]=(dst["class"]?dst["class"]+" ":"")+value):"style"==key?($element.attr("style",$element.attr("style")+";"+value),dst.style=(dst.style?dst.style+";":"")+value):"$"==key.charAt(0)||dst.hasOwnProperty(key)||(dst[key]=value,dstAttr[key]=srcAttr[key])})}function compileTemplateUrl(directives,$compileNode,tAttrs,$rootElement,childTranscludeFn,preLinkFns,postLinkFns,previousCompileContext){var afterTemplateNodeLinkFn,afterTemplateChildLinkFn,linkQueue=[],beforeTemplateCompileNode=$compileNode[0],origAsyncDirective=directives.shift(),derivedSyncDirective=inherit(origAsyncDirective,{templateUrl:null,transclude:null,replace:null,$$originalDirective:origAsyncDirective}),templateUrl=isFunction(origAsyncDirective.templateUrl)?origAsyncDirective.templateUrl($compileNode,tAttrs):origAsyncDirective.templateUrl,templateNamespace=origAsyncDirective.templateNamespace;return $compileNode.empty(),$templateRequest(templateUrl).then(function(content){var compileNode,tempTemplateAttrs,$template,childBoundTranscludeFn;if(content=denormalizeTemplate(content),origAsyncDirective.replace){if($template=jqLiteIsTextNode(content)?[]:removeComments(wrapTemplate(templateNamespace,trim(content))),compileNode=$template[0],1!=$template.length||compileNode.nodeType!==NODE_TYPE_ELEMENT)throw $compileMinErr("tplrt","Template for directive '{0}' must have exactly one root element. {1}",origAsyncDirective.name,templateUrl);tempTemplateAttrs={$attr:{}},replaceWith($rootElement,$compileNode,compileNode);var templateDirectives=collectDirectives(compileNode,[],tempTemplateAttrs);isObject(origAsyncDirective.scope)&&markDirectivesAsIsolate(templateDirectives),directives=templateDirectives.concat(directives),mergeTemplateAttributes(tAttrs,tempTemplateAttrs)}else compileNode=beforeTemplateCompileNode,$compileNode.html(content);for(directives.unshift(derivedSyncDirective),afterTemplateNodeLinkFn=applyDirectivesToNode(directives,compileNode,tAttrs,childTranscludeFn,$compileNode,origAsyncDirective,preLinkFns,postLinkFns,previousCompileContext),forEach($rootElement,function(node,i){node==compileNode&&($rootElement[i]=$compileNode[0])}),afterTemplateChildLinkFn=compileNodes($compileNode[0].childNodes,childTranscludeFn);linkQueue.length;){var scope=linkQueue.shift(),beforeTemplateLinkNode=linkQueue.shift(),linkRootElement=linkQueue.shift(),boundTranscludeFn=linkQueue.shift(),linkNode=$compileNode[0];if(!scope.$$destroyed){if(beforeTemplateLinkNode!==beforeTemplateCompileNode){var oldClasses=beforeTemplateLinkNode.className;previousCompileContext.hasElementTranscludeDirective&&origAsyncDirective.replace||(linkNode=jqLiteClone(compileNode)),replaceWith(linkRootElement,jqLite(beforeTemplateLinkNode),linkNode),safeAddClass(jqLite(linkNode),oldClasses)}childBoundTranscludeFn=afterTemplateNodeLinkFn.transcludeOnThisElement?createBoundTranscludeFn(scope,afterTemplateNodeLinkFn.transclude,boundTranscludeFn):boundTranscludeFn,afterTemplateNodeLinkFn(afterTemplateChildLinkFn,scope,linkNode,$rootElement,childBoundTranscludeFn,afterTemplateNodeLinkFn)}}linkQueue=null}),function(ignoreChildLinkFn,scope,node,rootElement,boundTranscludeFn){var childBoundTranscludeFn=boundTranscludeFn;scope.$$destroyed||(linkQueue?linkQueue.push(scope,node,rootElement,childBoundTranscludeFn):(afterTemplateNodeLinkFn.transcludeOnThisElement&&(childBoundTranscludeFn=createBoundTranscludeFn(scope,afterTemplateNodeLinkFn.transclude,boundTranscludeFn)),afterTemplateNodeLinkFn(afterTemplateChildLinkFn,scope,node,rootElement,childBoundTranscludeFn,afterTemplateNodeLinkFn)))}}function byPriority(a,b){var diff=b.priority-a.priority;return 0!==diff?diff:a.name!==b.name?a.name"+template+"",wrapper.childNodes[0].childNodes;default:return template}}function getTrustedContext(node,attrNormalizedName){if("srcdoc"==attrNormalizedName)return $sce.HTML;var tag=nodeName_(node);return"xlinkHref"==attrNormalizedName||"form"==tag&&"action"==attrNormalizedName||"img"!=tag&&("src"==attrNormalizedName||"ngSrc"==attrNormalizedName)?$sce.RESOURCE_URL:void 0}function addAttrInterpolateDirective(node,directives,value,name,allOrNothing){var trustedContext=getTrustedContext(node,name);allOrNothing=ALL_OR_NOTHING_ATTRS[name]||allOrNothing;var interpolateFn=$interpolate(value,!0,trustedContext,allOrNothing);if(interpolateFn){if("multiple"===name&&"select"===nodeName_(node))throw $compileMinErr("selmulti","Binding to the 'multiple' attribute is not supported. Element: {0}",startingTag(node));directives.push({priority:100,compile:function(){return{pre:function(scope,element,attr){var $$observers=attr.$$observers||(attr.$$observers={});if(EVENT_HANDLER_ATTR_REGEXP.test(name))throw $compileMinErr("nodomevents","Interpolations for HTML DOM event attributes are disallowed. Please use the ng- versions (such as ng-click instead of onclick) instead.");var newValue=attr[name];newValue!==value&&(interpolateFn=newValue&&$interpolate(newValue,!0,trustedContext,allOrNothing),value=newValue),interpolateFn&&(attr[name]=interpolateFn(scope),($$observers[name]||($$observers[name]=[])).$$inter=!0,(attr.$$observers&&attr.$$observers[name].$$scope||scope).$watch(interpolateFn,function(newValue,oldValue){"class"===name&&newValue!=oldValue?attr.$updateClass(newValue,oldValue):attr.$set(name,newValue)}))}}}})}}function replaceWith($rootElement,elementsToRemove,newNode){var i,ii,firstElementToRemove=elementsToRemove[0],removeCount=elementsToRemove.length,parent=firstElementToRemove.parentNode;if($rootElement)for(i=0,ii=$rootElement.length;ii>i;i++)if($rootElement[i]==firstElementToRemove){$rootElement[i++]=newNode;for(var j=i,j2=j+removeCount-1,jj=$rootElement.length;jj>j;j++,j2++)jj>j2?$rootElement[j]=$rootElement[j2]:delete $rootElement[j];$rootElement.length-=removeCount-1,$rootElement.context===firstElementToRemove&&($rootElement.context=newNode);break}parent&&parent.replaceChild(newNode,firstElementToRemove);var fragment=document.createDocumentFragment();fragment.appendChild(firstElementToRemove),jqLite.hasData(firstElementToRemove)&&(jqLite(newNode).data(jqLite(firstElementToRemove).data()),jQuery?(skipDestroyOnNextJQueryCleanData=!0,jQuery.cleanData([firstElementToRemove])):delete jqLite.cache[firstElementToRemove[jqLite.expando]]);for(var k=1,kk=elementsToRemove.length;kk>k;k++){var element=elementsToRemove[k];jqLite(element).remove(),fragment.appendChild(element),delete elementsToRemove[k]}elementsToRemove[0]=newNode,elementsToRemove.length=1}function cloneAndAnnotateFn(fn,annotation){return extend(function(){return fn.apply(null,arguments)},fn,annotation)}function invokeLinkFn(linkFn,scope,$element,attrs,controllers,transcludeFn){try{linkFn(scope,$element,attrs,controllers,transcludeFn)}catch(e){$exceptionHandler(e,startingTag($element))}}function initializeDirectiveBindings(scope,attrs,destination,bindings,directive,newScope){var onNewScopeDestroyed;forEach(bindings,function(definition,scopeName){var lastValue,parentGet,parentSet,compare,attrName=definition.attrName,optional=definition.optional,mode=definition.mode;switch(mode){case"@":optional||hasOwnProperty.call(attrs,attrName)||(destination[scopeName]=attrs[attrName]=void 0),attrs.$observe(attrName,function(value){isString(value)&&(destination[scopeName]=value)}),attrs.$$observers[attrName].$$scope=scope,isString(attrs[attrName])&&(destination[scopeName]=$interpolate(attrs[attrName])(scope));break;case"=":if(!hasOwnProperty.call(attrs,attrName)){if(optional)break;attrs[attrName]=void 0}if(optional&&!attrs[attrName])break;parentGet=$parse(attrs[attrName]),compare=parentGet.literal?equals:function(a,b){return a===b||a!==a&&b!==b},parentSet=parentGet.assign||function(){throw lastValue=destination[scopeName]=parentGet(scope),$compileMinErr("nonassign","Expression '{0}' used with directive '{1}' is non-assignable!",attrs[attrName],directive.name)},lastValue=destination[scopeName]=parentGet(scope);var parentValueWatch=function(parentValue){return compare(parentValue,destination[scopeName])||(compare(parentValue,lastValue)?parentSet(scope,parentValue=destination[scopeName]):destination[scopeName]=parentValue),lastValue=parentValue};parentValueWatch.$stateful=!0;var unwatch;unwatch=definition.collection?scope.$watchCollection(attrs[attrName],parentValueWatch):scope.$watch($parse(attrs[attrName],parentValueWatch),null,parentGet.literal),onNewScopeDestroyed=onNewScopeDestroyed||[],onNewScopeDestroyed.push(unwatch);break;case"&":if(parentGet=attrs.hasOwnProperty(attrName)?$parse(attrs[attrName]):noop,parentGet===noop&&optional)break;destination[scopeName]=function(locals){return parentGet(scope,locals)}}});var destroyBindings=onNewScopeDestroyed?function(){for(var i=0,ii=onNewScopeDestroyed.length;ii>i;++i)onNewScopeDestroyed[i]()}:noop;return newScope&&destroyBindings!==noop?(newScope.$on("$destroy",destroyBindings),noop):destroyBindings}var Attributes=function(element,attributesToCopy){if(attributesToCopy){var i,l,key,keys=Object.keys(attributesToCopy);for(i=0,l=keys.length;l>i;i++)key=keys[i],this[key]=attributesToCopy[key]}else this.$attr={};this.$$element=element};Attributes.prototype={$normalize:directiveNormalize,$addClass:function(classVal){classVal&&classVal.length>0&&$animate.addClass(this.$$element,classVal)},$removeClass:function(classVal){classVal&&classVal.length>0&&$animate.removeClass(this.$$element,classVal)},$updateClass:function(newClasses,oldClasses){var toAdd=tokenDifference(newClasses,oldClasses);toAdd&&toAdd.length&&$animate.addClass(this.$$element,toAdd);var toRemove=tokenDifference(oldClasses,newClasses);toRemove&&toRemove.length&&$animate.removeClass(this.$$element,toRemove)},$set:function(key,value,writeAttr,attrName){var nodeName,node=this.$$element[0],booleanKey=getBooleanAttrName(node,key),aliasedKey=getAliasedAttrName(node,key),observer=key;if(booleanKey?(this.$$element.prop(key,value),attrName=booleanKey):aliasedKey&&(this[aliasedKey]=value,observer=aliasedKey),this[key]=value,attrName?this.$attr[key]=attrName:(attrName=this.$attr[key],attrName||(this.$attr[key]=attrName=snake_case(key,"-"))),nodeName=nodeName_(this.$$element),"a"===nodeName&&"href"===key||"img"===nodeName&&"src"===key)this[key]=value=$$sanitizeUri(value,"src"===key);else if("img"===nodeName&&"srcset"===key){for(var result="",trimmedSrcset=trim(value),srcPattern=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,pattern=/\s/.test(trimmedSrcset)?srcPattern:/(,)/,rawUris=trimmedSrcset.split(pattern),nbrUrisWith2parts=Math.floor(rawUris.length/2),i=0;nbrUrisWith2parts>i;i++){var innerIdx=2*i;result+=$$sanitizeUri(trim(rawUris[innerIdx]),!0),result+=" "+trim(rawUris[innerIdx+1])}var lastTuple=trim(rawUris[2*i]).split(/\s/);result+=$$sanitizeUri(trim(lastTuple[0]),!0),2===lastTuple.length&&(result+=" "+trim(lastTuple[1])),this[key]=value=result}writeAttr!==!1&&(null===value||value===undefined?this.$$element.removeAttr(attrName):this.$$element.attr(attrName,value));var $$observers=this.$$observers;$$observers&&forEach($$observers[observer],function(fn){try{fn(value)}catch(e){$exceptionHandler(e)}})},$observe:function(key,fn){var attrs=this,$$observers=attrs.$$observers||(attrs.$$observers=createMap()),listeners=$$observers[key]||($$observers[key]=[]);return listeners.push(fn),$rootScope.$evalAsync(function(){listeners.$$inter||!attrs.hasOwnProperty(key)||isUndefined(attrs[key])||fn(attrs[key])}),function(){arrayRemove(listeners,fn)}}};var startSymbol=$interpolate.startSymbol(),endSymbol=$interpolate.endSymbol(),denormalizeTemplate="{{"==startSymbol||"}}"==endSymbol?identity:function(template){return template.replace(/\{\{/g,startSymbol).replace(/}}/g,endSymbol)},NG_ATTR_BINDING=/^ngAttr[A-Z]/;return compile.$$addBindingInfo=debugInfoEnabled?function($element,binding){var bindings=$element.data("$binding")||[];isArray(binding)?bindings=bindings.concat(binding):bindings.push(binding),$element.data("$binding",bindings)}:noop,compile.$$addBindingClass=debugInfoEnabled?function($element){safeAddClass($element,"ng-binding")}:noop,compile.$$addScopeInfo=debugInfoEnabled?function($element,scope,isolated,noTemplate){var dataName=isolated?noTemplate?"$isolateScopeNoTemplate":"$isolateScope":"$scope";$element.data(dataName,scope)}:noop,compile.$$addScopeClass=debugInfoEnabled?function($element,isolated){safeAddClass($element,isolated?"ng-isolate-scope":"ng-scope")}:noop,compile}]}function directiveNormalize(name){return camelCase(name.replace(PREFIX_REGEXP,""))}function tokenDifference(str1,str2){var values="",tokens1=str1.split(/\s+/),tokens2=str2.split(/\s+/);outer:for(var i=0;i0?" ":"")+token}return values}function removeComments(jqNodes){jqNodes=jqLite(jqNodes);var i=jqNodes.length;if(1>=i)return jqNodes;for(;i--;){var node=jqNodes[i];node.nodeType===NODE_TYPE_COMMENT&&splice.call(jqNodes,i,1)}return jqNodes}function identifierForController(controller,ident){if(ident&&isString(ident))return ident;if(isString(controller)){var match=CNTRL_REG.exec(controller);if(match)return match[3]}}function $ControllerProvider(){var controllers={},globals=!1;this.register=function(name,constructor){assertNotHasOwnProperty(name,"controller"),isObject(name)?extend(controllers,name):controllers[name]=constructor},this.allowGlobals=function(){globals=!0},this.$get=["$injector","$window",function($injector,$window){function addIdentifier(locals,identifier,instance,name){if(!locals||!isObject(locals.$scope))throw minErr("$controller")("noscp","Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",name,identifier);locals.$scope[identifier]=instance}return function(expression,locals,later,ident){var instance,match,constructor,identifier;if(later=later===!0,ident&&isString(ident)&&(identifier=ident),isString(expression)){if(match=expression.match(CNTRL_REG),!match)throw $controllerMinErr("ctrlfmt","Badly formed controller string '{0}'. Must match `__name__ as __id__` or `__name__`.",expression);constructor=match[1],identifier=identifier||match[3],expression=controllers.hasOwnProperty(constructor)?controllers[constructor]:getter(locals.$scope,constructor,!0)||(globals?getter($window,constructor,!0):undefined),assertArgFn(expression,constructor,!0)}if(later){var controllerPrototype=(isArray(expression)?expression[expression.length-1]:expression).prototype;instance=Object.create(controllerPrototype||null),identifier&&addIdentifier(locals,identifier,instance,constructor||expression.name);var instantiate;return instantiate=extend(function(){var result=$injector.invoke(expression,instance,locals,constructor);return result!==instance&&(isObject(result)||isFunction(result))&&(instance=result,identifier&&addIdentifier(locals,identifier,instance,constructor||expression.name)),instance},{instance:instance,identifier:identifier})}return instance=$injector.instantiate(expression,locals,constructor),identifier&&addIdentifier(locals,identifier,instance,constructor||expression.name),instance}}]}function $DocumentProvider(){this.$get=["$window",function(window){return jqLite(window.document)}]}function $ExceptionHandlerProvider(){this.$get=["$log",function($log){return function(exception,cause){$log.error.apply($log,arguments)}}]}function serializeValue(v){return isObject(v)?isDate(v)?v.toISOString():toJson(v):v}function $HttpParamSerializerProvider(){this.$get=function(){return function(params){if(!params)return"";var parts=[];return forEachSorted(params,function(value,key){null===value||isUndefined(value)||(isArray(value)?forEach(value,function(v,k){parts.push(encodeUriQuery(key)+"="+encodeUriQuery(serializeValue(v)))}):parts.push(encodeUriQuery(key)+"="+encodeUriQuery(serializeValue(value))))}),parts.join("&")}}}function $HttpParamSerializerJQLikeProvider(){this.$get=function(){return function(params){function serialize(toSerialize,prefix,topLevel){null===toSerialize||isUndefined(toSerialize)||(isArray(toSerialize)?forEach(toSerialize,function(value,index){serialize(value,prefix+"["+(isObject(value)?index:"")+"]")}):isObject(toSerialize)&&!isDate(toSerialize)?forEachSorted(toSerialize,function(value,key){serialize(value,prefix+(topLevel?"":"[")+key+(topLevel?"":"]"))}):parts.push(encodeUriQuery(prefix)+"="+encodeUriQuery(serializeValue(toSerialize))))}if(!params)return"";var parts=[];return serialize(params,"",!0),parts.join("&")}}}function defaultHttpResponseTransform(data,headers){if(isString(data)){var tempData=data.replace(JSON_PROTECTION_PREFIX,"").trim();if(tempData){var contentType=headers("Content-Type");(contentType&&0===contentType.indexOf(APPLICATION_JSON)||isJsonLike(tempData))&&(data=fromJson(tempData))}}return data}function isJsonLike(str){var jsonStart=str.match(JSON_START);return jsonStart&&JSON_ENDS[jsonStart[0]].test(str)}function parseHeaders(headers){function fillInParsed(key,val){key&&(parsed[key]=parsed[key]?parsed[key]+", "+val:val)}var i,parsed=createMap();return isString(headers)?forEach(headers.split("\n"),function(line){i=line.indexOf(":"),fillInParsed(lowercase(trim(line.substr(0,i))),trim(line.substr(i+1)))}):isObject(headers)&&forEach(headers,function(headerVal,headerKey){fillInParsed(lowercase(headerKey),trim(headerVal))}),parsed}function headersGetter(headers){var headersObj;return function(name){if(headersObj||(headersObj=parseHeaders(headers)),name){var value=headersObj[lowercase(name)];return void 0===value&&(value=null),value}return headersObj}}function transformData(data,headers,status,fns){return isFunction(fns)?fns(data,headers,status):(forEach(fns,function(fn){data=fn(data,headers,status)}),data)}function isSuccess(status){return status>=200&&300>status}function $HttpProvider(){var defaults=this.defaults={transformResponse:[defaultHttpResponseTransform],transformRequest:[function(d){return!isObject(d)||isFile(d)||isBlob(d)||isFormData(d)?d:toJson(d)}],headers:{common:{Accept:"application/json, text/plain, */*"},post:shallowCopy(CONTENT_TYPE_APPLICATION_JSON),put:shallowCopy(CONTENT_TYPE_APPLICATION_JSON),patch:shallowCopy(CONTENT_TYPE_APPLICATION_JSON)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},useApplyAsync=!1;this.useApplyAsync=function(value){return isDefined(value)?(useApplyAsync=!!value,this):useApplyAsync};var useLegacyPromise=!0;this.useLegacyPromiseExtensions=function(value){return isDefined(value)?(useLegacyPromise=!!value,this):useLegacyPromise};var interceptorFactories=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function($httpBackend,$$cookieReader,$cacheFactory,$rootScope,$q,$injector){function $http(requestConfig){function transformResponse(response){var resp=extend({},response);return response.data?resp.data=transformData(response.data,response.headers,response.status,config.transformResponse):resp.data=response.data,isSuccess(response.status)?resp:$q.reject(resp)}function executeHeaderFns(headers,config){var headerContent,processedHeaders={};return forEach(headers,function(headerFn,header){isFunction(headerFn)?(headerContent=headerFn(config),null!=headerContent&&(processedHeaders[header]=headerContent)):processedHeaders[header]=headerFn}),processedHeaders}function mergeHeaders(config){var defHeaderName,lowercaseDefHeaderName,reqHeaderName,defHeaders=defaults.headers,reqHeaders=extend({},config.headers);defHeaders=extend({},defHeaders.common,defHeaders[lowercase(config.method)]);defaultHeadersIteration:for(defHeaderName in defHeaders){lowercaseDefHeaderName=lowercase(defHeaderName);for(reqHeaderName in reqHeaders)if(lowercase(reqHeaderName)===lowercaseDefHeaderName)continue defaultHeadersIteration;reqHeaders[defHeaderName]=defHeaders[defHeaderName]}return executeHeaderFns(reqHeaders,shallowCopy(config))}if(!angular.isObject(requestConfig))throw minErr("$http")("badreq","Http request configuration must be an object. Received: {0}",requestConfig);var config=extend({method:"get",transformRequest:defaults.transformRequest,transformResponse:defaults.transformResponse,paramSerializer:defaults.paramSerializer},requestConfig);config.headers=mergeHeaders(requestConfig),config.method=uppercase(config.method),config.paramSerializer=isString(config.paramSerializer)?$injector.get(config.paramSerializer):config.paramSerializer;var serverRequest=function(config){var headers=config.headers,reqData=transformData(config.data,headersGetter(headers),undefined,config.transformRequest);return isUndefined(reqData)&&forEach(headers,function(value,header){"content-type"===lowercase(header)&&delete headers[header]}),isUndefined(config.withCredentials)&&!isUndefined(defaults.withCredentials)&&(config.withCredentials=defaults.withCredentials),sendReq(config,reqData).then(transformResponse,transformResponse)},chain=[serverRequest,undefined],promise=$q.when(config);for(forEach(reversedInterceptors,function(interceptor){(interceptor.request||interceptor.requestError)&&chain.unshift(interceptor.request,interceptor.requestError),(interceptor.response||interceptor.responseError)&&chain.push(interceptor.response,interceptor.responseError)});chain.length;){var thenFn=chain.shift(),rejectFn=chain.shift();promise=promise.then(thenFn,rejectFn)}return useLegacyPromise?(promise.success=function(fn){return assertArgFn(fn,"fn"),promise.then(function(response){fn(response.data,response.status,response.headers,config)}),promise},promise.error=function(fn){return assertArgFn(fn,"fn"),promise.then(null,function(response){fn(response.data,response.status,response.headers,config)}),promise}):(promise.success=$httpMinErrLegacyFn("success"),promise.error=$httpMinErrLegacyFn("error")),promise}function createShortMethods(names){forEach(arguments,function(name){$http[name]=function(url,config){return $http(extend({},config||{},{method:name,url:url}))}})}function createShortMethodsWithData(name){forEach(arguments,function(name){$http[name]=function(url,data,config){return $http(extend({},config||{},{method:name,url:url,data:data}))}})}function sendReq(config,reqData){function done(status,response,headersString,statusText){function resolveHttpPromise(){resolvePromise(response,status,headersString,statusText)}cache&&(isSuccess(status)?cache.put(url,[status,response,parseHeaders(headersString),statusText]):cache.remove(url)),useApplyAsync?$rootScope.$applyAsync(resolveHttpPromise):(resolveHttpPromise(),$rootScope.$$phase||$rootScope.$apply())}function resolvePromise(response,status,headers,statusText){status=Math.max(status,0),(isSuccess(status)?deferred.resolve:deferred.reject)({data:response,status:status,headers:headersGetter(headers),config:config,statusText:statusText})}function resolvePromiseWithResult(result){resolvePromise(result.data,result.status,shallowCopy(result.headers()),result.statusText)}function removePendingReq(){var idx=$http.pendingRequests.indexOf(config);-1!==idx&&$http.pendingRequests.splice(idx,1)}var cache,cachedResp,deferred=$q.defer(),promise=deferred.promise,reqHeaders=config.headers,url=buildUrl(config.url,config.paramSerializer(config.params));if($http.pendingRequests.push(config),promise.then(removePendingReq,removePendingReq),!config.cache&&!defaults.cache||config.cache===!1||"GET"!==config.method&&"JSONP"!==config.method||(cache=isObject(config.cache)?config.cache:isObject(defaults.cache)?defaults.cache:defaultCache),cache&&(cachedResp=cache.get(url),isDefined(cachedResp)?isPromiseLike(cachedResp)?cachedResp.then(resolvePromiseWithResult,resolvePromiseWithResult):isArray(cachedResp)?resolvePromise(cachedResp[1],cachedResp[0],shallowCopy(cachedResp[2]),cachedResp[3]):resolvePromise(cachedResp,200,{},"OK"):cache.put(url,promise)),isUndefined(cachedResp)){var xsrfValue=urlIsSameOrigin(config.url)?$$cookieReader()[config.xsrfCookieName||defaults.xsrfCookieName]:undefined;xsrfValue&&(reqHeaders[config.xsrfHeaderName||defaults.xsrfHeaderName]=xsrfValue),$httpBackend(config.method,url,reqData,done,reqHeaders,config.timeout,config.withCredentials,config.responseType)}return promise}function buildUrl(url,serializedParams){return serializedParams.length>0&&(url+=(-1==url.indexOf("?")?"?":"&")+serializedParams),url}var defaultCache=$cacheFactory("$http");defaults.paramSerializer=isString(defaults.paramSerializer)?$injector.get(defaults.paramSerializer):defaults.paramSerializer;var reversedInterceptors=[];return forEach(interceptorFactories,function(interceptorFactory){reversedInterceptors.unshift(isString(interceptorFactory)?$injector.get(interceptorFactory):$injector.invoke(interceptorFactory))}),$http.pendingRequests=[],createShortMethods("get","delete","head","jsonp"),createShortMethodsWithData("post","put","patch"),$http.defaults=defaults,$http}]}function createXhr(){return new window.XMLHttpRequest}function $HttpBackendProvider(){this.$get=["$browser","$window","$document",function($browser,$window,$document){return createHttpBackend($browser,createXhr,$browser.defer,$window.angular.callbacks,$document[0])}]}function createHttpBackend($browser,createXhr,$browserDefer,callbacks,rawDocument){function jsonpReq(url,callbackId,done){var script=rawDocument.createElement("script"),callback=null;return script.type="text/javascript",script.src=url,script.async=!0,callback=function(event){removeEventListenerFn(script,"load",callback),removeEventListenerFn(script,"error",callback),rawDocument.body.removeChild(script),script=null;var status=-1,text="unknown";event&&("load"!==event.type||callbacks[callbackId].called||(event={type:"error"}),text=event.type,status="error"===event.type?404:200),done&&done(status,text)},addEventListenerFn(script,"load",callback),addEventListenerFn(script,"error",callback),rawDocument.body.appendChild(script),callback}return function(method,url,post,callback,headers,timeout,withCredentials,responseType){function timeoutRequest(){jsonpDone&&jsonpDone(),xhr&&xhr.abort()}function completeRequest(callback,status,response,headersString,statusText){timeoutId!==undefined&&$browserDefer.cancel(timeoutId),jsonpDone=xhr=null,callback(status,response,headersString,statusText),$browser.$$completeOutstandingRequest(noop)}if($browser.$$incOutstandingRequestCount(),url=url||$browser.url(),"jsonp"==lowercase(method)){var callbackId="_"+(callbacks.counter++).toString(36);callbacks[callbackId]=function(data){callbacks[callbackId].data=data,callbacks[callbackId].called=!0};var jsonpDone=jsonpReq(url.replace("JSON_CALLBACK","angular.callbacks."+callbackId),callbackId,function(status,text){completeRequest(callback,status,callbacks[callbackId].data,"",text),callbacks[callbackId]=noop})}else{var xhr=createXhr();xhr.open(method,url,!0),forEach(headers,function(value,key){isDefined(value)&&xhr.setRequestHeader(key,value)}),xhr.onload=function(){var statusText=xhr.statusText||"",response="response"in xhr?xhr.response:xhr.responseText,status=1223===xhr.status?204:xhr.status;0===status&&(status=response?200:"file"==urlResolve(url).protocol?404:0),completeRequest(callback,status,response,xhr.getAllResponseHeaders(),statusText)};var requestError=function(){completeRequest(callback,-1,null,null,"")};if(xhr.onerror=requestError,xhr.onabort=requestError,withCredentials&&(xhr.withCredentials=!0),responseType)try{xhr.responseType=responseType}catch(e){if("json"!==responseType)throw e}xhr.send(post)}if(timeout>0)var timeoutId=$browserDefer(timeoutRequest,timeout);else isPromiseLike(timeout)&&timeout.then(timeoutRequest)}}function $InterpolateProvider(){var startSymbol="{{",endSymbol="}}";this.startSymbol=function(value){return value?(startSymbol=value,this):startSymbol},this.endSymbol=function(value){return value?(endSymbol=value,this):endSymbol},this.$get=["$parse","$exceptionHandler","$sce",function($parse,$exceptionHandler,$sce){function escape(ch){return"\\\\\\"+ch}function unescapeText(text){return text.replace(escapedStartRegexp,startSymbol).replace(escapedEndRegexp,endSymbol)}function stringify(value){if(null==value)return"";switch(typeof value){case"string":break;case"number":value=""+value;break;default:value=toJson(value)}return value}function $interpolate(text,mustHaveExpression,trustedContext,allOrNothing){function parseStringifyInterceptor(value){try{return value=getValue(value),allOrNothing&&!isDefined(value)?value:stringify(value)}catch(err){$exceptionHandler($interpolateMinErr.interr(text,err))}}allOrNothing=!!allOrNothing;for(var startIndex,endIndex,exp,index=0,expressions=[],parseFns=[],textLength=text.length,concat=[],expressionPositions=[];textLength>index;){if(-1==(startIndex=text.indexOf(startSymbol,index))||-1==(endIndex=text.indexOf(endSymbol,startIndex+startSymbolLength))){index!==textLength&&concat.push(unescapeText(text.substring(index)));break}index!==startIndex&&concat.push(unescapeText(text.substring(index,startIndex))),exp=text.substring(startIndex+startSymbolLength,endIndex),expressions.push(exp),parseFns.push($parse(exp,parseStringifyInterceptor)),index=endIndex+endSymbolLength,expressionPositions.push(concat.length),concat.push("")}if(trustedContext&&concat.length>1&&$interpolateMinErr.throwNoconcat(text),!mustHaveExpression||expressions.length){var compute=function(values){for(var i=0,ii=expressions.length;ii>i;i++){if(allOrNothing&&isUndefined(values[i]))return;concat[expressionPositions[i]]=values[i]}return concat.join("")},getValue=function(value){return trustedContext?$sce.getTrusted(trustedContext,value):$sce.valueOf(value)};return extend(function(context){var i=0,ii=expressions.length,values=new Array(ii);try{for(;ii>i;i++)values[i]=parseFns[i](context); +return compute(values)}catch(err){$exceptionHandler($interpolateMinErr.interr(text,err))}},{exp:text,expressions:expressions,$$watchDelegate:function(scope,listener){var lastValue;return scope.$watchGroup(parseFns,function(values,oldValues){var currValue=compute(values);isFunction(listener)&&listener.call(this,currValue,values!==oldValues?lastValue:currValue,scope),lastValue=currValue})}})}}var startSymbolLength=startSymbol.length,endSymbolLength=endSymbol.length,escapedStartRegexp=new RegExp(startSymbol.replace(/./g,escape),"g"),escapedEndRegexp=new RegExp(endSymbol.replace(/./g,escape),"g");return $interpolate.startSymbol=function(){return startSymbol},$interpolate.endSymbol=function(){return endSymbol},$interpolate}]}function $IntervalProvider(){this.$get=["$rootScope","$window","$q","$$q",function($rootScope,$window,$q,$$q){function interval(fn,delay,count,invokeApply){var hasParams=arguments.length>4,args=hasParams?sliceArgs(arguments,4):[],setInterval=$window.setInterval,clearInterval=$window.clearInterval,iteration=0,skipApply=isDefined(invokeApply)&&!invokeApply,deferred=(skipApply?$$q:$q).defer(),promise=deferred.promise;return count=isDefined(count)?count:0,promise.then(null,null,hasParams?function(){fn.apply(null,args)}:fn),promise.$$intervalId=setInterval(function(){deferred.notify(iteration++),count>0&&iteration>=count&&(deferred.resolve(iteration),clearInterval(promise.$$intervalId),delete intervals[promise.$$intervalId]),skipApply||$rootScope.$apply()},delay),intervals[promise.$$intervalId]=deferred,promise}var intervals={};return interval.cancel=function(promise){return promise&&promise.$$intervalId in intervals?(intervals[promise.$$intervalId].reject("canceled"),$window.clearInterval(promise.$$intervalId),delete intervals[promise.$$intervalId],!0):!1},interval}]}function encodePath(path){for(var segments=path.split("/"),i=segments.length;i--;)segments[i]=encodeUriSegment(segments[i]);return segments.join("/")}function parseAbsoluteUrl(absoluteUrl,locationObj){var parsedUrl=urlResolve(absoluteUrl);locationObj.$$protocol=parsedUrl.protocol,locationObj.$$host=parsedUrl.hostname,locationObj.$$port=toInt(parsedUrl.port)||DEFAULT_PORTS[parsedUrl.protocol]||null}function parseAppUrl(relativeUrl,locationObj){var prefixed="/"!==relativeUrl.charAt(0);prefixed&&(relativeUrl="/"+relativeUrl);var match=urlResolve(relativeUrl);locationObj.$$path=decodeURIComponent(prefixed&&"/"===match.pathname.charAt(0)?match.pathname.substring(1):match.pathname),locationObj.$$search=parseKeyValue(match.search),locationObj.$$hash=decodeURIComponent(match.hash),locationObj.$$path&&"/"!=locationObj.$$path.charAt(0)&&(locationObj.$$path="/"+locationObj.$$path)}function beginsWith(begin,whole){return 0===whole.indexOf(begin)?whole.substr(begin.length):void 0}function stripHash(url){var index=url.indexOf("#");return-1==index?url:url.substr(0,index)}function trimEmptyHash(url){return url.replace(/(#.+)|#$/,"$1")}function stripFile(url){return url.substr(0,stripHash(url).lastIndexOf("/")+1)}function serverBase(url){return url.substring(0,url.indexOf("/",url.indexOf("//")+2))}function LocationHtml5Url(appBase,appBaseNoFile,basePrefix){this.$$html5=!0,basePrefix=basePrefix||"",parseAbsoluteUrl(appBase,this),this.$$parse=function(url){var pathUrl=beginsWith(appBaseNoFile,url);if(!isString(pathUrl))throw $locationMinErr("ipthprfx",'Invalid url "{0}", missing path prefix "{1}".',url,appBaseNoFile);parseAppUrl(pathUrl,this),this.$$path||(this.$$path="/"),this.$$compose()},this.$$compose=function(){var search=toKeyValue(this.$$search),hash=this.$$hash?"#"+encodeUriSegment(this.$$hash):"";this.$$url=encodePath(this.$$path)+(search?"?"+search:"")+hash,this.$$absUrl=appBaseNoFile+this.$$url.substr(1)},this.$$parseLinkUrl=function(url,relHref){if(relHref&&"#"===relHref[0])return this.hash(relHref.slice(1)),!0;var appUrl,prevAppUrl,rewrittenUrl;return(appUrl=beginsWith(appBase,url))!==undefined?(prevAppUrl=appUrl,rewrittenUrl=(appUrl=beginsWith(basePrefix,appUrl))!==undefined?appBaseNoFile+(beginsWith("/",appUrl)||appUrl):appBase+prevAppUrl):(appUrl=beginsWith(appBaseNoFile,url))!==undefined?rewrittenUrl=appBaseNoFile+appUrl:appBaseNoFile==url+"/"&&(rewrittenUrl=appBaseNoFile),rewrittenUrl&&this.$$parse(rewrittenUrl),!!rewrittenUrl}}function LocationHashbangUrl(appBase,appBaseNoFile,hashPrefix){parseAbsoluteUrl(appBase,this),this.$$parse=function(url){function removeWindowsDriveName(path,url,base){var firstPathSegmentMatch,windowsFilePathExp=/^\/[A-Z]:(\/.*)/;return 0===url.indexOf(base)&&(url=url.replace(base,"")),windowsFilePathExp.exec(url)?path:(firstPathSegmentMatch=windowsFilePathExp.exec(path),firstPathSegmentMatch?firstPathSegmentMatch[1]:path)}var withoutHashUrl,withoutBaseUrl=beginsWith(appBase,url)||beginsWith(appBaseNoFile,url);isUndefined(withoutBaseUrl)||"#"!==withoutBaseUrl.charAt(0)?this.$$html5?withoutHashUrl=withoutBaseUrl:(withoutHashUrl="",isUndefined(withoutBaseUrl)&&(appBase=url,this.replace())):(withoutHashUrl=beginsWith(hashPrefix,withoutBaseUrl),isUndefined(withoutHashUrl)&&(withoutHashUrl=withoutBaseUrl)),parseAppUrl(withoutHashUrl,this),this.$$path=removeWindowsDriveName(this.$$path,withoutHashUrl,appBase),this.$$compose()},this.$$compose=function(){var search=toKeyValue(this.$$search),hash=this.$$hash?"#"+encodeUriSegment(this.$$hash):"";this.$$url=encodePath(this.$$path)+(search?"?"+search:"")+hash,this.$$absUrl=appBase+(this.$$url?hashPrefix+this.$$url:"")},this.$$parseLinkUrl=function(url,relHref){return stripHash(appBase)==stripHash(url)?(this.$$parse(url),!0):!1}}function LocationHashbangInHtml5Url(appBase,appBaseNoFile,hashPrefix){this.$$html5=!0,LocationHashbangUrl.apply(this,arguments),this.$$parseLinkUrl=function(url,relHref){if(relHref&&"#"===relHref[0])return this.hash(relHref.slice(1)),!0;var rewrittenUrl,appUrl;return appBase==stripHash(url)?rewrittenUrl=url:(appUrl=beginsWith(appBaseNoFile,url))?rewrittenUrl=appBase+hashPrefix+appUrl:appBaseNoFile===url+"/"&&(rewrittenUrl=appBaseNoFile),rewrittenUrl&&this.$$parse(rewrittenUrl),!!rewrittenUrl},this.$$compose=function(){var search=toKeyValue(this.$$search),hash=this.$$hash?"#"+encodeUriSegment(this.$$hash):"";this.$$url=encodePath(this.$$path)+(search?"?"+search:"")+hash,this.$$absUrl=appBase+hashPrefix+this.$$url}}function locationGetter(property){return function(){return this[property]}}function locationGetterSetter(property,preprocess){return function(value){return isUndefined(value)?this[property]:(this[property]=preprocess(value),this.$$compose(),this)}}function $LocationProvider(){var hashPrefix="",html5Mode={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(prefix){return isDefined(prefix)?(hashPrefix=prefix,this):hashPrefix},this.html5Mode=function(mode){return isBoolean(mode)?(html5Mode.enabled=mode,this):isObject(mode)?(isBoolean(mode.enabled)&&(html5Mode.enabled=mode.enabled),isBoolean(mode.requireBase)&&(html5Mode.requireBase=mode.requireBase),isBoolean(mode.rewriteLinks)&&(html5Mode.rewriteLinks=mode.rewriteLinks),this):html5Mode},this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function($rootScope,$browser,$sniffer,$rootElement,$window){function setBrowserUrlWithFallback(url,replace,state){var oldUrl=$location.url(),oldState=$location.$$state;try{$browser.url(url,replace,state),$location.$$state=$browser.state()}catch(e){throw $location.url(oldUrl),$location.$$state=oldState,e}}function afterLocationChange(oldUrl,oldState){$rootScope.$broadcast("$locationChangeSuccess",$location.absUrl(),oldUrl,$location.$$state,oldState)}var $location,LocationMode,appBase,baseHref=$browser.baseHref(),initialUrl=$browser.url();if(html5Mode.enabled){if(!baseHref&&html5Mode.requireBase)throw $locationMinErr("nobase","$location in HTML5 mode requires a tag to be present!");appBase=serverBase(initialUrl)+(baseHref||"/"),LocationMode=$sniffer.history?LocationHtml5Url:LocationHashbangInHtml5Url}else appBase=stripHash(initialUrl),LocationMode=LocationHashbangUrl;var appBaseNoFile=stripFile(appBase);$location=new LocationMode(appBase,appBaseNoFile,"#"+hashPrefix),$location.$$parseLinkUrl(initialUrl,initialUrl),$location.$$state=$browser.state();var IGNORE_URI_REGEXP=/^\s*(javascript|mailto):/i;$rootElement.on("click",function(event){if(html5Mode.rewriteLinks&&!event.ctrlKey&&!event.metaKey&&!event.shiftKey&&2!=event.which&&2!=event.button){for(var elm=jqLite(event.target);"a"!==nodeName_(elm[0]);)if(elm[0]===$rootElement[0]||!(elm=elm.parent())[0])return;var absHref=elm.prop("href"),relHref=elm.attr("href")||elm.attr("xlink:href");isObject(absHref)&&"[object SVGAnimatedString]"===absHref.toString()&&(absHref=urlResolve(absHref.animVal).href),IGNORE_URI_REGEXP.test(absHref)||!absHref||elm.attr("target")||event.isDefaultPrevented()||$location.$$parseLinkUrl(absHref,relHref)&&(event.preventDefault(),$location.absUrl()!=$browser.url()&&($rootScope.$apply(),$window.angular["ff-684208-preventDefault"]=!0))}}),trimEmptyHash($location.absUrl())!=trimEmptyHash(initialUrl)&&$browser.url($location.absUrl(),!0);var initializing=!0;return $browser.onUrlChange(function(newUrl,newState){return isUndefined(beginsWith(appBaseNoFile,newUrl))?void($window.location.href=newUrl):($rootScope.$evalAsync(function(){var defaultPrevented,oldUrl=$location.absUrl(),oldState=$location.$$state;$location.$$parse(newUrl),$location.$$state=newState,defaultPrevented=$rootScope.$broadcast("$locationChangeStart",newUrl,oldUrl,newState,oldState).defaultPrevented,$location.absUrl()===newUrl&&(defaultPrevented?($location.$$parse(oldUrl),$location.$$state=oldState,setBrowserUrlWithFallback(oldUrl,!1,oldState)):(initializing=!1,afterLocationChange(oldUrl,oldState)))}),void($rootScope.$$phase||$rootScope.$digest()))}),$rootScope.$watch(function(){var oldUrl=trimEmptyHash($browser.url()),newUrl=trimEmptyHash($location.absUrl()),oldState=$browser.state(),currentReplace=$location.$$replace,urlOrStateChanged=oldUrl!==newUrl||$location.$$html5&&$sniffer.history&&oldState!==$location.$$state;(initializing||urlOrStateChanged)&&(initializing=!1,$rootScope.$evalAsync(function(){var newUrl=$location.absUrl(),defaultPrevented=$rootScope.$broadcast("$locationChangeStart",newUrl,oldUrl,$location.$$state,oldState).defaultPrevented;$location.absUrl()===newUrl&&(defaultPrevented?($location.$$parse(oldUrl),$location.$$state=oldState):(urlOrStateChanged&&setBrowserUrlWithFallback(newUrl,currentReplace,oldState===$location.$$state?null:$location.$$state),afterLocationChange(oldUrl,oldState)))})),$location.$$replace=!1}),$location}]}function $LogProvider(){var debug=!0,self=this;this.debugEnabled=function(flag){return isDefined(flag)?(debug=flag,this):debug},this.$get=["$window",function($window){function formatError(arg){return arg instanceof Error&&(arg.stack?arg=arg.message&&-1===arg.stack.indexOf(arg.message)?"Error: "+arg.message+"\n"+arg.stack:arg.stack:arg.sourceURL&&(arg=arg.message+"\n"+arg.sourceURL+":"+arg.line)),arg}function consoleLog(type){var console=$window.console||{},logFn=console[type]||console.log||noop,hasApply=!1;try{hasApply=!!logFn.apply}catch(e){}return hasApply?function(){var args=[];return forEach(arguments,function(arg){args.push(formatError(arg))}),logFn.apply(console,args)}:function(arg1,arg2){logFn(arg1,null==arg2?"":arg2)}}return{log:consoleLog("log"),info:consoleLog("info"),warn:consoleLog("warn"),error:consoleLog("error"),debug:function(){var fn=consoleLog("debug");return function(){debug&&fn.apply(self,arguments)}}()}}]}function ensureSafeMemberName(name,fullExpression){if("__defineGetter__"===name||"__defineSetter__"===name||"__lookupGetter__"===name||"__lookupSetter__"===name||"__proto__"===name)throw $parseMinErr("isecfld","Attempting to access a disallowed field in Angular expressions! Expression: {0}",fullExpression);return name}function ensureSafeObject(obj,fullExpression){if(obj){if(obj.constructor===obj)throw $parseMinErr("isecfn","Referencing Function in Angular expressions is disallowed! Expression: {0}",fullExpression);if(obj.window===obj)throw $parseMinErr("isecwindow","Referencing the Window in Angular expressions is disallowed! Expression: {0}",fullExpression);if(obj.children&&(obj.nodeName||obj.prop&&obj.attr&&obj.find))throw $parseMinErr("isecdom","Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}",fullExpression);if(obj===Object)throw $parseMinErr("isecobj","Referencing Object in Angular expressions is disallowed! Expression: {0}",fullExpression)}return obj}function ensureSafeFunction(obj,fullExpression){if(obj){if(obj.constructor===obj)throw $parseMinErr("isecfn","Referencing Function in Angular expressions is disallowed! Expression: {0}",fullExpression);if(obj===CALL||obj===APPLY||obj===BIND)throw $parseMinErr("isecff","Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}",fullExpression)}}function ifDefined(v,d){return"undefined"!=typeof v?v:d}function plusFn(l,r){return"undefined"==typeof l?r:"undefined"==typeof r?l:l+r}function isStateless($filter,filterName){var fn=$filter(filterName);return!fn.$stateful}function findConstantAndWatchExpressions(ast,$filter){var allConstants,argsToWatch;switch(ast.type){case AST.Program:allConstants=!0,forEach(ast.body,function(expr){findConstantAndWatchExpressions(expr.expression,$filter),allConstants=allConstants&&expr.expression.constant}),ast.constant=allConstants;break;case AST.Literal:ast.constant=!0,ast.toWatch=[];break;case AST.UnaryExpression:findConstantAndWatchExpressions(ast.argument,$filter),ast.constant=ast.argument.constant,ast.toWatch=ast.argument.toWatch;break;case AST.BinaryExpression:findConstantAndWatchExpressions(ast.left,$filter),findConstantAndWatchExpressions(ast.right,$filter),ast.constant=ast.left.constant&&ast.right.constant,ast.toWatch=ast.left.toWatch.concat(ast.right.toWatch);break;case AST.LogicalExpression:findConstantAndWatchExpressions(ast.left,$filter),findConstantAndWatchExpressions(ast.right,$filter),ast.constant=ast.left.constant&&ast.right.constant,ast.toWatch=ast.constant?[]:[ast];break;case AST.ConditionalExpression:findConstantAndWatchExpressions(ast.test,$filter),findConstantAndWatchExpressions(ast.alternate,$filter),findConstantAndWatchExpressions(ast.consequent,$filter),ast.constant=ast.test.constant&&ast.alternate.constant&&ast.consequent.constant,ast.toWatch=ast.constant?[]:[ast];break;case AST.Identifier:ast.constant=!1,ast.toWatch=[ast];break;case AST.MemberExpression:findConstantAndWatchExpressions(ast.object,$filter),ast.computed&&findConstantAndWatchExpressions(ast.property,$filter),ast.constant=ast.object.constant&&(!ast.computed||ast.property.constant),ast.toWatch=[ast];break;case AST.CallExpression:allConstants=ast.filter?isStateless($filter,ast.callee.name):!1,argsToWatch=[],forEach(ast.arguments,function(expr){findConstantAndWatchExpressions(expr,$filter),allConstants=allConstants&&expr.constant,expr.constant||argsToWatch.push.apply(argsToWatch,expr.toWatch)}),ast.constant=allConstants,ast.toWatch=ast.filter&&isStateless($filter,ast.callee.name)?argsToWatch:[ast];break;case AST.AssignmentExpression:findConstantAndWatchExpressions(ast.left,$filter),findConstantAndWatchExpressions(ast.right,$filter),ast.constant=ast.left.constant&&ast.right.constant,ast.toWatch=[ast];break;case AST.ArrayExpression:allConstants=!0,argsToWatch=[],forEach(ast.elements,function(expr){findConstantAndWatchExpressions(expr,$filter),allConstants=allConstants&&expr.constant,expr.constant||argsToWatch.push.apply(argsToWatch,expr.toWatch)}),ast.constant=allConstants,ast.toWatch=argsToWatch;break;case AST.ObjectExpression:allConstants=!0,argsToWatch=[],forEach(ast.properties,function(property){findConstantAndWatchExpressions(property.value,$filter),allConstants=allConstants&&property.value.constant,property.value.constant||argsToWatch.push.apply(argsToWatch,property.value.toWatch)}),ast.constant=allConstants,ast.toWatch=argsToWatch;break;case AST.ThisExpression:ast.constant=!1,ast.toWatch=[]}}function getInputs(body){if(1==body.length){var lastExpression=body[0].expression,candidate=lastExpression.toWatch;return 1!==candidate.length?candidate:candidate[0]!==lastExpression?candidate:undefined}}function isAssignable(ast){return ast.type===AST.Identifier||ast.type===AST.MemberExpression}function assignableAST(ast){return 1===ast.body.length&&isAssignable(ast.body[0].expression)?{type:AST.AssignmentExpression,left:ast.body[0].expression,right:{type:AST.NGValueParameter},operator:"="}:void 0}function isLiteral(ast){return 0===ast.body.length||1===ast.body.length&&(ast.body[0].expression.type===AST.Literal||ast.body[0].expression.type===AST.ArrayExpression||ast.body[0].expression.type===AST.ObjectExpression)}function isConstant(ast){return ast.constant}function ASTCompiler(astBuilder,$filter){this.astBuilder=astBuilder,this.$filter=$filter}function ASTInterpreter(astBuilder,$filter){this.astBuilder=astBuilder,this.$filter=$filter}function isPossiblyDangerousMemberName(name){return"constructor"==name}function getValueOf(value){return isFunction(value.valueOf)?value.valueOf():objectValueOf.call(value)}function $ParseProvider(){var cacheDefault=createMap(),cacheExpensive=createMap();this.$get=["$filter",function($filter){function expressionInputDirtyCheck(newValue,oldValueOfValue){return null==newValue||null==oldValueOfValue?newValue===oldValueOfValue:"object"==typeof newValue&&(newValue=getValueOf(newValue),"object"==typeof newValue)?!1:newValue===oldValueOfValue||newValue!==newValue&&oldValueOfValue!==oldValueOfValue}function inputsWatchDelegate(scope,listener,objectEquality,parsedExpression,prettyPrintExpression){var lastResult,inputExpressions=parsedExpression.inputs;if(1===inputExpressions.length){var oldInputValueOf=expressionInputDirtyCheck;return inputExpressions=inputExpressions[0],scope.$watch(function(scope){var newInputValue=inputExpressions(scope);return expressionInputDirtyCheck(newInputValue,oldInputValueOf)||(lastResult=parsedExpression(scope,undefined,undefined,[newInputValue]),oldInputValueOf=newInputValue&&getValueOf(newInputValue)),lastResult},listener,objectEquality,prettyPrintExpression)}for(var oldInputValueOfValues=[],oldInputValues=[],i=0,ii=inputExpressions.length;ii>i;i++)oldInputValueOfValues[i]=expressionInputDirtyCheck,oldInputValues[i]=null;return scope.$watch(function(scope){for(var changed=!1,i=0,ii=inputExpressions.length;ii>i;i++){var newInputValue=inputExpressions[i](scope);(changed||(changed=!expressionInputDirtyCheck(newInputValue,oldInputValueOfValues[i])))&&(oldInputValues[i]=newInputValue,oldInputValueOfValues[i]=newInputValue&&getValueOf(newInputValue))}return changed&&(lastResult=parsedExpression(scope,undefined,undefined,oldInputValues)),lastResult},listener,objectEquality,prettyPrintExpression)}function oneTimeWatchDelegate(scope,listener,objectEquality,parsedExpression){var unwatch,lastValue;return unwatch=scope.$watch(function(scope){return parsedExpression(scope)},function(value,old,scope){lastValue=value,isFunction(listener)&&listener.apply(this,arguments),isDefined(value)&&scope.$$postDigest(function(){isDefined(lastValue)&&unwatch()})},objectEquality)}function oneTimeLiteralWatchDelegate(scope,listener,objectEquality,parsedExpression){function isAllDefined(value){var allDefined=!0;return forEach(value,function(val){isDefined(val)||(allDefined=!1)}),allDefined}var unwatch,lastValue;return unwatch=scope.$watch(function(scope){return parsedExpression(scope)},function(value,old,scope){lastValue=value,isFunction(listener)&&listener.call(this,value,old,scope),isAllDefined(value)&&scope.$$postDigest(function(){isAllDefined(lastValue)&&unwatch()})},objectEquality)}function constantWatchDelegate(scope,listener,objectEquality,parsedExpression){var unwatch;return unwatch=scope.$watch(function(scope){return parsedExpression(scope)},function(value,old,scope){isFunction(listener)&&listener.apply(this,arguments),unwatch()},objectEquality)}function addInterceptor(parsedExpression,interceptorFn){if(!interceptorFn)return parsedExpression;var watchDelegate=parsedExpression.$$watchDelegate,regularWatch=watchDelegate!==oneTimeLiteralWatchDelegate&&watchDelegate!==oneTimeWatchDelegate,fn=regularWatch?function(scope,locals,assign,inputs){var value=parsedExpression(scope,locals,assign,inputs);return interceptorFn(value,scope,locals)}:function(scope,locals,assign,inputs){var value=parsedExpression(scope,locals,assign,inputs),result=interceptorFn(value,scope,locals);return isDefined(value)?result:value};return parsedExpression.$$watchDelegate&&parsedExpression.$$watchDelegate!==inputsWatchDelegate?fn.$$watchDelegate=parsedExpression.$$watchDelegate:interceptorFn.$stateful||(fn.$$watchDelegate=inputsWatchDelegate,fn.inputs=parsedExpression.inputs?parsedExpression.inputs:[parsedExpression]),fn}var noUnsafeEval=csp().noUnsafeEval,$parseOptions={csp:noUnsafeEval,expensiveChecks:!1},$parseOptionsExpensive={csp:noUnsafeEval,expensiveChecks:!0};return function(exp,interceptorFn,expensiveChecks){var parsedExpression,oneTime,cacheKey;switch(typeof exp){case"string":exp=exp.trim(),cacheKey=exp;var cache=expensiveChecks?cacheExpensive:cacheDefault;if(parsedExpression=cache[cacheKey],!parsedExpression){":"===exp.charAt(0)&&":"===exp.charAt(1)&&(oneTime=!0,exp=exp.substring(2));var parseOptions=expensiveChecks?$parseOptionsExpensive:$parseOptions,lexer=new Lexer(parseOptions),parser=new Parser(lexer,$filter,parseOptions);parsedExpression=parser.parse(exp),parsedExpression.constant?parsedExpression.$$watchDelegate=constantWatchDelegate:oneTime?parsedExpression.$$watchDelegate=parsedExpression.literal?oneTimeLiteralWatchDelegate:oneTimeWatchDelegate:parsedExpression.inputs&&(parsedExpression.$$watchDelegate=inputsWatchDelegate),cache[cacheKey]=parsedExpression}return addInterceptor(parsedExpression,interceptorFn);case"function":return addInterceptor(exp,interceptorFn);default:return noop}}}]}function $QProvider(){this.$get=["$rootScope","$exceptionHandler",function($rootScope,$exceptionHandler){return qFactory(function(callback){$rootScope.$evalAsync(callback)},$exceptionHandler)}]}function $$QProvider(){this.$get=["$browser","$exceptionHandler",function($browser,$exceptionHandler){return qFactory(function(callback){$browser.defer(callback)},$exceptionHandler)}]}function qFactory(nextTick,exceptionHandler){function callOnce(self,resolveFn,rejectFn){function wrap(fn){return function(value){called||(called=!0,fn.call(self,value))}}var called=!1;return[wrap(resolveFn),wrap(rejectFn)]}function Promise(){this.$$state={status:0}}function simpleBind(context,fn){return function(value){fn.call(context,value)}}function processQueue(state){var fn,deferred,pending;pending=state.pending,state.processScheduled=!1,state.pending=undefined;for(var i=0,ii=pending.length;ii>i;++i){deferred=pending[i][0],fn=pending[i][state.status];try{isFunction(fn)?deferred.resolve(fn(state.value)):1===state.status?deferred.resolve(state.value):deferred.reject(state.value)}catch(e){deferred.reject(e),exceptionHandler(e)}}}function scheduleProcessQueue(state){!state.processScheduled&&state.pending&&(state.processScheduled=!0,nextTick(function(){processQueue(state)}))}function Deferred(){this.promise=new Promise,this.resolve=simpleBind(this,this.resolve),this.reject=simpleBind(this,this.reject),this.notify=simpleBind(this,this.notify)}function all(promises){var deferred=new Deferred,counter=0,results=isArray(promises)?[]:{};return forEach(promises,function(promise,key){counter++,when(promise).then(function(value){results.hasOwnProperty(key)||(results[key]=value,--counter||deferred.resolve(results))},function(reason){results.hasOwnProperty(key)||deferred.reject(reason)})}),0===counter&&deferred.resolve(results),deferred.promise}var $qMinErr=minErr("$q",TypeError),defer=function(){return new Deferred};extend(Promise.prototype,{then:function(onFulfilled,onRejected,progressBack){if(isUndefined(onFulfilled)&&isUndefined(onRejected)&&isUndefined(progressBack))return this;var result=new Deferred;return this.$$state.pending=this.$$state.pending||[],this.$$state.pending.push([result,onFulfilled,onRejected,progressBack]),this.$$state.status>0&&scheduleProcessQueue(this.$$state),result.promise},"catch":function(callback){return this.then(null,callback)},"finally":function(callback,progressBack){return this.then(function(value){return handleCallback(value,!0,callback)},function(error){return handleCallback(error,!1,callback)},progressBack)}}),extend(Deferred.prototype,{resolve:function(val){this.promise.$$state.status||(val===this.promise?this.$$reject($qMinErr("qcycle","Expected promise to be resolved with value other than itself '{0}'",val)):this.$$resolve(val))},$$resolve:function(val){var then,fns;fns=callOnce(this,this.$$resolve,this.$$reject);try{(isObject(val)||isFunction(val))&&(then=val&&val.then),isFunction(then)?(this.promise.$$state.status=-1,then.call(val,fns[0],fns[1],this.notify)):(this.promise.$$state.value=val,this.promise.$$state.status=1,scheduleProcessQueue(this.promise.$$state))}catch(e){fns[1](e),exceptionHandler(e)}},reject:function(reason){this.promise.$$state.status||this.$$reject(reason)},$$reject:function(reason){this.promise.$$state.value=reason,this.promise.$$state.status=2,scheduleProcessQueue(this.promise.$$state)},notify:function(progress){var callbacks=this.promise.$$state.pending;this.promise.$$state.status<=0&&callbacks&&callbacks.length&&nextTick(function(){for(var callback,result,i=0,ii=callbacks.length;ii>i;i++){result=callbacks[i][0],callback=callbacks[i][3];try{result.notify(isFunction(callback)?callback(progress):progress)}catch(e){exceptionHandler(e)}}})}});var reject=function(reason){var result=new Deferred;return result.reject(reason),result.promise},makePromise=function(value,resolved){var result=new Deferred;return resolved?result.resolve(value):result.reject(value),result.promise},handleCallback=function(value,isResolved,callback){var callbackOutput=null;try{isFunction(callback)&&(callbackOutput=callback())}catch(e){return makePromise(e,!1)}return isPromiseLike(callbackOutput)?callbackOutput.then(function(){return makePromise(value,isResolved)},function(error){return makePromise(error,!1)}):makePromise(value,isResolved)},when=function(value,callback,errback,progressBack){var result=new Deferred;return result.resolve(value),result.promise.then(callback,errback,progressBack)},resolve=when,$Q=function Q(resolver){function resolveFn(value){deferred.resolve(value)}function rejectFn(reason){deferred.reject(reason)}if(!isFunction(resolver))throw $qMinErr("norslvr","Expected resolverFn, got '{0}'",resolver);if(!(this instanceof Q))return new Q(resolver);var deferred=new Deferred;return resolver(resolveFn,rejectFn),deferred.promise};return $Q.defer=defer,$Q.reject=reject,$Q.when=when,$Q.resolve=resolve,$Q.all=all,$Q}function $$RAFProvider(){this.$get=["$window","$timeout",function($window,$timeout){function flush(){for(var i=0;i=0&&(taskQueue[index]=null,index=null,0===--taskCount&&cancelLastRAF&&(cancelLastRAF(),cancelLastRAF=null,taskQueue.length=0))}}var requestAnimationFrame=$window.requestAnimationFrame||$window.webkitRequestAnimationFrame,cancelAnimationFrame=$window.cancelAnimationFrame||$window.webkitCancelAnimationFrame||$window.webkitCancelRequestAnimationFrame,rafSupported=!!requestAnimationFrame,rafFn=rafSupported?function(fn){var id=requestAnimationFrame(fn);return function(){cancelAnimationFrame(id)}}:function(fn){var timer=$timeout(fn,16.66,!1);return function(){$timeout.cancel(timer)}};queueFn.supported=rafSupported;var cancelLastRAF,taskCount=0,taskQueue=[];return queueFn}]}function $RootScopeProvider(){function createChildScopeClass(parent){function ChildScope(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null,this.$$listeners={},this.$$listenerCount={},this.$$watchersCount=0,this.$id=nextUid(),this.$$ChildScope=null}return ChildScope.prototype=parent,ChildScope}var TTL=10,$rootScopeMinErr=minErr("$rootScope"),lastDirtyWatch=null,applyAsyncId=null;this.digestTtl=function(value){return arguments.length&&(TTL=value),TTL},this.$get=["$injector","$exceptionHandler","$parse","$browser",function($injector,$exceptionHandler,$parse,$browser){function destroyChildScope($event){$event.currentScope.$$destroyed=!0}function Scope(){this.$id=nextUid(),this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null,this.$root=this,this.$$destroyed=!1,this.$$listeners={},this.$$listenerCount={},this.$$watchersCount=0,this.$$isolateBindings=null}function beginPhase(phase){if($rootScope.$$phase)throw $rootScopeMinErr("inprog","{0} already in progress",$rootScope.$$phase);$rootScope.$$phase=phase}function clearPhase(){$rootScope.$$phase=null}function incrementWatchersCount(current,count){do current.$$watchersCount+=count;while(current=current.$parent)}function decrementListenerCount(current,count,name){do current.$$listenerCount[name]-=count,0===current.$$listenerCount[name]&&delete current.$$listenerCount[name];while(current=current.$parent)}function initWatchVal(){}function flushApplyAsync(){for(;applyAsyncQueue.length;)try{applyAsyncQueue.shift()()}catch(e){$exceptionHandler(e)}applyAsyncId=null}function scheduleApplyAsync(){null===applyAsyncId&&(applyAsyncId=$browser.defer(function(){$rootScope.$apply(flushApplyAsync)}))}Scope.prototype={constructor:Scope,$new:function(isolate,parent){var child;return parent=parent||this,isolate?(child=new Scope,child.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=createChildScopeClass(this)),child=new this.$$ChildScope),child.$parent=parent,child.$$prevSibling=parent.$$childTail,parent.$$childHead?(parent.$$childTail.$$nextSibling=child,parent.$$childTail=child):parent.$$childHead=parent.$$childTail=child,(isolate||parent!=this)&&child.$on("$destroy",destroyChildScope),child},$watch:function(watchExp,listener,objectEquality,prettyPrintExpression){var get=$parse(watchExp);if(get.$$watchDelegate)return get.$$watchDelegate(this,listener,objectEquality,get,watchExp);var scope=this,array=scope.$$watchers,watcher={fn:listener,last:initWatchVal,get:get,exp:prettyPrintExpression||watchExp,eq:!!objectEquality};return lastDirtyWatch=null,isFunction(listener)||(watcher.fn=noop),array||(array=scope.$$watchers=[]),array.unshift(watcher),incrementWatchersCount(this,1),function(){arrayRemove(array,watcher)>=0&&incrementWatchersCount(scope,-1),lastDirtyWatch=null}},$watchGroup:function(watchExpressions,listener){function watchGroupAction(){changeReactionScheduled=!1,firstRun?(firstRun=!1,listener(newValues,newValues,self)):listener(newValues,oldValues,self)}var oldValues=new Array(watchExpressions.length),newValues=new Array(watchExpressions.length),deregisterFns=[],self=this,changeReactionScheduled=!1,firstRun=!0;if(!watchExpressions.length){var shouldCall=!0;return self.$evalAsync(function(){shouldCall&&listener(newValues,newValues,self)}),function(){shouldCall=!1}}return 1===watchExpressions.length?this.$watch(watchExpressions[0],function(value,oldValue,scope){newValues[0]=value,oldValues[0]=oldValue,listener(newValues,value===oldValue?newValues:oldValues,scope)}):(forEach(watchExpressions,function(expr,i){var unwatchFn=self.$watch(expr,function(value,oldValue){newValues[i]=value,oldValues[i]=oldValue,changeReactionScheduled||(changeReactionScheduled=!0,self.$evalAsync(watchGroupAction))});deregisterFns.push(unwatchFn)}),function(){for(;deregisterFns.length;)deregisterFns.shift()()})},$watchCollection:function(obj,listener){function $watchCollectionInterceptor(_value){newValue=_value;var newLength,key,bothNaN,newItem,oldItem;if(!isUndefined(newValue)){if(isObject(newValue))if(isArrayLike(newValue)){oldValue!==internalArray&&(oldValue=internalArray, +oldLength=oldValue.length=0,changeDetected++),newLength=newValue.length,oldLength!==newLength&&(changeDetected++,oldValue.length=oldLength=newLength);for(var i=0;newLength>i;i++)oldItem=oldValue[i],newItem=newValue[i],bothNaN=oldItem!==oldItem&&newItem!==newItem,bothNaN||oldItem===newItem||(changeDetected++,oldValue[i]=newItem)}else{oldValue!==internalObject&&(oldValue=internalObject={},oldLength=0,changeDetected++),newLength=0;for(key in newValue)newValue.hasOwnProperty(key)&&(newLength++,newItem=newValue[key],oldItem=oldValue[key],key in oldValue?(bothNaN=oldItem!==oldItem&&newItem!==newItem,bothNaN||oldItem===newItem||(changeDetected++,oldValue[key]=newItem)):(oldLength++,oldValue[key]=newItem,changeDetected++));if(oldLength>newLength){changeDetected++;for(key in oldValue)newValue.hasOwnProperty(key)||(oldLength--,delete oldValue[key])}}else oldValue!==newValue&&(oldValue=newValue,changeDetected++);return changeDetected}}function $watchCollectionAction(){if(initRun?(initRun=!1,listener(newValue,newValue,self)):listener(newValue,veryOldValue,self),trackVeryOldValue)if(isObject(newValue))if(isArrayLike(newValue)){veryOldValue=new Array(newValue.length);for(var i=0;i1,changeDetected=0,changeDetector=$parse(obj,$watchCollectionInterceptor),internalArray=[],internalObject={},initRun=!0,oldLength=0;return this.$watch(changeDetector,$watchCollectionAction)},$digest:function(){var watch,value,last,watchers,length,dirty,next,current,logIdx,asyncTask,ttl=TTL,target=this,watchLog=[];beginPhase("$digest"),$browser.$$checkUrlChange(),this===$rootScope&&null!==applyAsyncId&&($browser.defer.cancel(applyAsyncId),flushApplyAsync()),lastDirtyWatch=null;do{for(dirty=!1,current=target;asyncQueue.length;){try{asyncTask=asyncQueue.shift(),asyncTask.scope.$eval(asyncTask.expression,asyncTask.locals)}catch(e){$exceptionHandler(e)}lastDirtyWatch=null}traverseScopesLoop:do{if(watchers=current.$$watchers)for(length=watchers.length;length--;)try{if(watch=watchers[length])if((value=watch.get(current))===(last=watch.last)||(watch.eq?equals(value,last):"number"==typeof value&&"number"==typeof last&&isNaN(value)&&isNaN(last))){if(watch===lastDirtyWatch){dirty=!1;break traverseScopesLoop}}else dirty=!0,lastDirtyWatch=watch,watch.last=watch.eq?copy(value,null):value,watch.fn(value,last===initWatchVal?value:last,current),5>ttl&&(logIdx=4-ttl,watchLog[logIdx]||(watchLog[logIdx]=[]),watchLog[logIdx].push({msg:isFunction(watch.exp)?"fn: "+(watch.exp.name||watch.exp.toString()):watch.exp,newVal:value,oldVal:last}))}catch(e){$exceptionHandler(e)}if(!(next=current.$$watchersCount&¤t.$$childHead||current!==target&¤t.$$nextSibling))for(;current!==target&&!(next=current.$$nextSibling);)current=current.$parent}while(current=next);if((dirty||asyncQueue.length)&&!ttl--)throw clearPhase(),$rootScopeMinErr("infdig","{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}",TTL,watchLog)}while(dirty||asyncQueue.length);for(clearPhase();postDigestQueue.length;)try{postDigestQueue.shift()()}catch(e){$exceptionHandler(e)}},$destroy:function(){if(!this.$$destroyed){var parent=this.$parent;this.$broadcast("$destroy"),this.$$destroyed=!0,this===$rootScope&&$browser.$$applicationDestroyed(),incrementWatchersCount(this,-this.$$watchersCount);for(var eventName in this.$$listenerCount)decrementListenerCount(this,this.$$listenerCount[eventName],eventName);parent&&parent.$$childHead==this&&(parent.$$childHead=this.$$nextSibling),parent&&parent.$$childTail==this&&(parent.$$childTail=this.$$prevSibling),this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling),this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling),this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=noop,this.$on=this.$watch=this.$watchGroup=function(){return noop},this.$$listeners={},this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}},$eval:function(expr,locals){return $parse(expr)(this,locals)},$evalAsync:function(expr,locals){$rootScope.$$phase||asyncQueue.length||$browser.defer(function(){asyncQueue.length&&$rootScope.$digest()}),asyncQueue.push({scope:this,expression:expr,locals:locals})},$$postDigest:function(fn){postDigestQueue.push(fn)},$apply:function(expr){try{beginPhase("$apply");try{return this.$eval(expr)}finally{clearPhase()}}catch(e){$exceptionHandler(e)}finally{try{$rootScope.$digest()}catch(e){throw $exceptionHandler(e),e}}},$applyAsync:function(expr){function $applyAsyncExpression(){scope.$eval(expr)}var scope=this;expr&&applyAsyncQueue.push($applyAsyncExpression),scheduleApplyAsync()},$on:function(name,listener){var namedListeners=this.$$listeners[name];namedListeners||(this.$$listeners[name]=namedListeners=[]),namedListeners.push(listener);var current=this;do current.$$listenerCount[name]||(current.$$listenerCount[name]=0),current.$$listenerCount[name]++;while(current=current.$parent);var self=this;return function(){var indexOfListener=namedListeners.indexOf(listener);-1!==indexOfListener&&(namedListeners[indexOfListener]=null,decrementListenerCount(self,1,name))}},$emit:function(name,args){var namedListeners,i,length,empty=[],scope=this,stopPropagation=!1,event={name:name,targetScope:scope,stopPropagation:function(){stopPropagation=!0},preventDefault:function(){event.defaultPrevented=!0},defaultPrevented:!1},listenerArgs=concat([event],arguments,1);do{for(namedListeners=scope.$$listeners[name]||empty,event.currentScope=scope,i=0,length=namedListeners.length;length>i;i++)if(namedListeners[i])try{namedListeners[i].apply(null,listenerArgs)}catch(e){$exceptionHandler(e)}else namedListeners.splice(i,1),i--,length--;if(stopPropagation)return event.currentScope=null,event;scope=scope.$parent}while(scope);return event.currentScope=null,event},$broadcast:function(name,args){var target=this,current=target,next=target,event={name:name,targetScope:target,preventDefault:function(){event.defaultPrevented=!0},defaultPrevented:!1};if(!target.$$listenerCount[name])return event;for(var listeners,i,length,listenerArgs=concat([event],arguments,1);current=next;){for(event.currentScope=current,listeners=current.$$listeners[name]||[],i=0,length=listeners.length;length>i;i++)if(listeners[i])try{listeners[i].apply(null,listenerArgs)}catch(e){$exceptionHandler(e)}else listeners.splice(i,1),i--,length--;if(!(next=current.$$listenerCount[name]&¤t.$$childHead||current!==target&¤t.$$nextSibling))for(;current!==target&&!(next=current.$$nextSibling);)current=current.$parent}return event.currentScope=null,event}};var $rootScope=new Scope,asyncQueue=$rootScope.$$asyncQueue=[],postDigestQueue=$rootScope.$$postDigestQueue=[],applyAsyncQueue=$rootScope.$$applyAsyncQueue=[];return $rootScope}]}function $$SanitizeUriProvider(){var aHrefSanitizationWhitelist=/^\s*(https?|ftp|mailto|tel|file):/,imgSrcSanitizationWhitelist=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(regexp){return isDefined(regexp)?(aHrefSanitizationWhitelist=regexp,this):aHrefSanitizationWhitelist},this.imgSrcSanitizationWhitelist=function(regexp){return isDefined(regexp)?(imgSrcSanitizationWhitelist=regexp,this):imgSrcSanitizationWhitelist},this.$get=function(){return function(uri,isImage){var normalizedVal,regex=isImage?imgSrcSanitizationWhitelist:aHrefSanitizationWhitelist;return normalizedVal=urlResolve(uri).href,""===normalizedVal||normalizedVal.match(regex)?uri:"unsafe:"+normalizedVal}}}function adjustMatcher(matcher){if("self"===matcher)return matcher;if(isString(matcher)){if(matcher.indexOf("***")>-1)throw $sceMinErr("iwcard","Illegal sequence *** in string matcher. String: {0}",matcher);return matcher=escapeForRegexp(matcher).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*"),new RegExp("^"+matcher+"$")}if(isRegExp(matcher))return new RegExp("^"+matcher.source+"$");throw $sceMinErr("imatcher",'Matchers may only be "self", string patterns or RegExp objects')}function adjustMatchers(matchers){var adjustedMatchers=[];return isDefined(matchers)&&forEach(matchers,function(matcher){adjustedMatchers.push(adjustMatcher(matcher))}),adjustedMatchers}function $SceDelegateProvider(){this.SCE_CONTEXTS=SCE_CONTEXTS;var resourceUrlWhitelist=["self"],resourceUrlBlacklist=[];this.resourceUrlWhitelist=function(value){return arguments.length&&(resourceUrlWhitelist=adjustMatchers(value)),resourceUrlWhitelist},this.resourceUrlBlacklist=function(value){return arguments.length&&(resourceUrlBlacklist=adjustMatchers(value)),resourceUrlBlacklist},this.$get=["$injector",function($injector){function matchUrl(matcher,parsedUrl){return"self"===matcher?urlIsSameOrigin(parsedUrl):!!matcher.exec(parsedUrl.href)}function isResourceUrlAllowedByPolicy(url){var i,n,parsedUrl=urlResolve(url.toString()),allowed=!1;for(i=0,n=resourceUrlWhitelist.length;n>i;i++)if(matchUrl(resourceUrlWhitelist[i],parsedUrl)){allowed=!0;break}if(allowed)for(i=0,n=resourceUrlBlacklist.length;n>i;i++)if(matchUrl(resourceUrlBlacklist[i],parsedUrl)){allowed=!1;break}return allowed}function generateHolderType(Base){var holderType=function(trustedValue){this.$$unwrapTrustedValue=function(){return trustedValue}};return Base&&(holderType.prototype=new Base),holderType.prototype.valueOf=function(){return this.$$unwrapTrustedValue()},holderType.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()},holderType}function trustAs(type,trustedValue){var Constructor=byType.hasOwnProperty(type)?byType[type]:null;if(!Constructor)throw $sceMinErr("icontext","Attempted to trust a value in invalid context. Context: {0}; Value: {1}",type,trustedValue);if(null===trustedValue||trustedValue===undefined||""===trustedValue)return trustedValue;if("string"!=typeof trustedValue)throw $sceMinErr("itype","Attempted to trust a non-string value in a content requiring a string: Context: {0}",type);return new Constructor(trustedValue)}function valueOf(maybeTrusted){return maybeTrusted instanceof trustedValueHolderBase?maybeTrusted.$$unwrapTrustedValue():maybeTrusted}function getTrusted(type,maybeTrusted){if(null===maybeTrusted||maybeTrusted===undefined||""===maybeTrusted)return maybeTrusted;var constructor=byType.hasOwnProperty(type)?byType[type]:null;if(constructor&&maybeTrusted instanceof constructor)return maybeTrusted.$$unwrapTrustedValue();if(type===SCE_CONTEXTS.RESOURCE_URL){if(isResourceUrlAllowedByPolicy(maybeTrusted))return maybeTrusted;throw $sceMinErr("insecurl","Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}",maybeTrusted.toString())}if(type===SCE_CONTEXTS.HTML)return htmlSanitizer(maybeTrusted);throw $sceMinErr("unsafe","Attempting to use an unsafe value in a safe context.")}var htmlSanitizer=function(html){throw $sceMinErr("unsafe","Attempting to use an unsafe value in a safe context.")};$injector.has("$sanitize")&&(htmlSanitizer=$injector.get("$sanitize"));var trustedValueHolderBase=generateHolderType(),byType={};return byType[SCE_CONTEXTS.HTML]=generateHolderType(trustedValueHolderBase),byType[SCE_CONTEXTS.CSS]=generateHolderType(trustedValueHolderBase),byType[SCE_CONTEXTS.URL]=generateHolderType(trustedValueHolderBase),byType[SCE_CONTEXTS.JS]=generateHolderType(trustedValueHolderBase),byType[SCE_CONTEXTS.RESOURCE_URL]=generateHolderType(byType[SCE_CONTEXTS.URL]),{trustAs:trustAs,getTrusted:getTrusted,valueOf:valueOf}}]}function $SceProvider(){var enabled=!0;this.enabled=function(value){return arguments.length&&(enabled=!!value),enabled},this.$get=["$parse","$sceDelegate",function($parse,$sceDelegate){if(enabled&&8>msie)throw $sceMinErr("iequirks","Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks mode. You can fix this by adding the text to the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more information.");var sce=shallowCopy(SCE_CONTEXTS);sce.isEnabled=function(){return enabled},sce.trustAs=$sceDelegate.trustAs,sce.getTrusted=$sceDelegate.getTrusted,sce.valueOf=$sceDelegate.valueOf,enabled||(sce.trustAs=sce.getTrusted=function(type,value){return value},sce.valueOf=identity),sce.parseAs=function(type,expr){var parsed=$parse(expr);return parsed.literal&&parsed.constant?parsed:$parse(expr,function(value){return sce.getTrusted(type,value)})};var parse=sce.parseAs,getTrusted=sce.getTrusted,trustAs=sce.trustAs;return forEach(SCE_CONTEXTS,function(enumValue,name){var lName=lowercase(name);sce[camelCase("parse_as_"+lName)]=function(expr){return parse(enumValue,expr)},sce[camelCase("get_trusted_"+lName)]=function(value){return getTrusted(enumValue,value)},sce[camelCase("trust_as_"+lName)]=function(value){return trustAs(enumValue,value)}}),sce}]}function $SnifferProvider(){this.$get=["$window","$document",function($window,$document){var vendorPrefix,match,eventSupport={},android=toInt((/android (\d+)/.exec(lowercase(($window.navigator||{}).userAgent))||[])[1]),boxee=/Boxee/i.test(($window.navigator||{}).userAgent),document=$document[0]||{},vendorRegex=/^(Moz|webkit|ms)(?=[A-Z])/,bodyStyle=document.body&&document.body.style,transitions=!1,animations=!1;if(bodyStyle){for(var prop in bodyStyle)if(match=vendorRegex.exec(prop)){vendorPrefix=match[0],vendorPrefix=vendorPrefix.substr(0,1).toUpperCase()+vendorPrefix.substr(1);break}vendorPrefix||(vendorPrefix="WebkitOpacity"in bodyStyle&&"webkit"),transitions=!!("transition"in bodyStyle||vendorPrefix+"Transition"in bodyStyle),animations=!!("animation"in bodyStyle||vendorPrefix+"Animation"in bodyStyle),!android||transitions&&animations||(transitions=isString(bodyStyle.webkitTransition),animations=isString(bodyStyle.webkitAnimation))}return{history:!(!$window.history||!$window.history.pushState||4>android||boxee),hasEvent:function(event){if("input"===event&&11>=msie)return!1;if(isUndefined(eventSupport[event])){var divElm=document.createElement("div");eventSupport[event]="on"+event in divElm}return eventSupport[event]},csp:csp(),vendorPrefix:vendorPrefix,transitions:transitions,animations:animations,android:android}}]}function $TemplateRequestProvider(){this.$get=["$templateCache","$http","$q","$sce",function($templateCache,$http,$q,$sce){function handleRequestFn(tpl,ignoreRequestError){function handleError(resp){if(!ignoreRequestError)throw $compileMinErr("tpload","Failed to load template: {0} (HTTP status: {1} {2})",tpl,resp.status,resp.statusText);return $q.reject(resp)}handleRequestFn.totalPendingRequests++,isString(tpl)&&$templateCache.get(tpl)||(tpl=$sce.getTrustedResourceUrl(tpl));var transformResponse=$http.defaults&&$http.defaults.transformResponse;isArray(transformResponse)?transformResponse=transformResponse.filter(function(transformer){return transformer!==defaultHttpResponseTransform}):transformResponse===defaultHttpResponseTransform&&(transformResponse=null);var httpOptions={cache:$templateCache,transformResponse:transformResponse};return $http.get(tpl,httpOptions)["finally"](function(){handleRequestFn.totalPendingRequests--}).then(function(response){return $templateCache.put(tpl,response.data),response.data},handleError)}return handleRequestFn.totalPendingRequests=0,handleRequestFn}]}function $$TestabilityProvider(){this.$get=["$rootScope","$browser","$location",function($rootScope,$browser,$location){var testability={};return testability.findBindings=function(element,expression,opt_exactMatch){var bindings=element.getElementsByClassName("ng-binding"),matches=[];return forEach(bindings,function(binding){var dataBinding=angular.element(binding).data("$binding");dataBinding&&forEach(dataBinding,function(bindingName){if(opt_exactMatch){var matcher=new RegExp("(^|\\s)"+escapeForRegexp(expression)+"(\\s|\\||$)");matcher.test(bindingName)&&matches.push(binding)}else-1!=bindingName.indexOf(expression)&&matches.push(binding)})}),matches},testability.findModels=function(element,expression,opt_exactMatch){for(var prefixes=["ng-","data-ng-","ng\\:"],p=0;p0&&(name=safeDecodeURIComponent(cookie.substring(0,index)),lastCookies[name]===undefined&&(lastCookies[name]=safeDecodeURIComponent(cookie.substring(index+1))));return lastCookies}}function $$CookieReaderProvider(){this.$get=$$CookieReader}function $FilterProvider($provide){function register(name,factory){if(isObject(name)){var filters={};return forEach(name,function(filter,key){filters[key]=register(key,filter)}),filters}return $provide.factory(name+suffix,factory)}var suffix="Filter";this.register=register,this.$get=["$injector",function($injector){return function(name){return $injector.get(name+suffix)}}],register("currency",currencyFilter),register("date",dateFilter),register("filter",filterFilter),register("json",jsonFilter),register("limitTo",limitToFilter),register("lowercase",lowercaseFilter),register("number",numberFilter),register("orderBy",orderByFilter),register("uppercase",uppercaseFilter)}function filterFilter(){return function(array,expression,comparator){if(!isArrayLike(array)){if(null==array)return array;throw minErr("filter")("notarray","Expected array but received: {0}",array)}var predicateFn,matchAgainstAnyProp,expressionType=getTypeForFilter(expression);switch(expressionType){case"function":predicateFn=expression;break;case"boolean":case"null":case"number":case"string":matchAgainstAnyProp=!0;case"object":predicateFn=createPredicateFn(expression,comparator,matchAgainstAnyProp);break;default:return array}return Array.prototype.filter.call(array,predicateFn)}}function createPredicateFn(expression,comparator,matchAgainstAnyProp){var predicateFn,shouldMatchPrimitives=isObject(expression)&&"$"in expression;return comparator===!0?comparator=equals:isFunction(comparator)||(comparator=function(actual,expected){return isUndefined(actual)?!1:null===actual||null===expected?actual===expected:isObject(expected)||isObject(actual)&&!hasCustomToString(actual)?!1:(actual=lowercase(""+actual),expected=lowercase(""+expected),-1!==actual.indexOf(expected))}),predicateFn=function(item){return shouldMatchPrimitives&&!isObject(item)?deepCompare(item,expression.$,comparator,!1):deepCompare(item,expression,comparator,matchAgainstAnyProp)}}function deepCompare(actual,expected,comparator,matchAgainstAnyProp,dontMatchWholeObject){var actualType=getTypeForFilter(actual),expectedType=getTypeForFilter(expected);if("string"===expectedType&&"!"===expected.charAt(0))return!deepCompare(actual,expected.substring(1),comparator,matchAgainstAnyProp);if(isArray(actual))return actual.some(function(item){return deepCompare(item,expected,comparator,matchAgainstAnyProp)});switch(actualType){case"object":var key;if(matchAgainstAnyProp){for(key in actual)if("$"!==key.charAt(0)&&deepCompare(actual[key],expected,comparator,!0))return!0;return dontMatchWholeObject?!1:deepCompare(actual,expected,comparator,!1)}if("object"===expectedType){for(key in expected){var expectedVal=expected[key];if(!isFunction(expectedVal)&&!isUndefined(expectedVal)){var matchAnyProperty="$"===key,actualVal=matchAnyProperty?actual:actual[key];if(!deepCompare(actualVal,expectedVal,comparator,matchAnyProperty,matchAnyProperty))return!1}}return!0}return comparator(actual,expected);case"function":return!1;default:return comparator(actual,expected)}}function getTypeForFilter(val){return null===val?"null":typeof val}function currencyFilter($locale){var formats=$locale.NUMBER_FORMATS;return function(amount,currencySymbol,fractionSize){return isUndefined(currencySymbol)&&(currencySymbol=formats.CURRENCY_SYM),isUndefined(fractionSize)&&(fractionSize=formats.PATTERNS[1].maxFrac),null==amount?amount:formatNumber(amount,formats.PATTERNS[1],formats.GROUP_SEP,formats.DECIMAL_SEP,fractionSize).replace(/\u00A4/g,currencySymbol)}}function numberFilter($locale){var formats=$locale.NUMBER_FORMATS;return function(number,fractionSize){return null==number?number:formatNumber(number,formats.PATTERNS[0],formats.GROUP_SEP,formats.DECIMAL_SEP,fractionSize)}}function formatNumber(number,pattern,groupSep,decimalSep,fractionSize){if(isObject(number))return"";var isNegative=0>number;number=Math.abs(number);var isInfinity=number===1/0;if(!isInfinity&&!isFinite(number))return"";var numStr=number+"",formatedText="",hasExponent=!1,parts=[];if(isInfinity&&(formatedText="∞"),!isInfinity&&-1!==numStr.indexOf("e")){var match=numStr.match(/([\d\.]+)e(-?)(\d+)/);match&&"-"==match[2]&&match[3]>fractionSize+1?number=0:(formatedText=numStr,hasExponent=!0)}if(isInfinity||hasExponent)fractionSize>0&&1>number&&(formatedText=number.toFixed(fractionSize),number=parseFloat(formatedText));else{var fractionLen=(numStr.split(DECIMAL_SEP)[1]||"").length;isUndefined(fractionSize)&&(fractionSize=Math.min(Math.max(pattern.minFrac,fractionLen),pattern.maxFrac)),number=+(Math.round(+(number.toString()+"e"+fractionSize)).toString()+"e"+-fractionSize);var fraction=(""+number).split(DECIMAL_SEP),whole=fraction[0];fraction=fraction[1]||"";var i,pos=0,lgroup=pattern.lgSize,group=pattern.gSize;if(whole.length>=lgroup+group)for(pos=whole.length-lgroup,i=0;pos>i;i++)(pos-i)%group===0&&0!==i&&(formatedText+=groupSep),formatedText+=whole.charAt(i);for(i=pos;inum&&(neg="-",num=-num),num=""+num;num.length0||value>-offset)&&(value+=offset),0===value&&-12==offset&&(value=12),padNumber(value,size,trim)}}function dateStrGetter(name,shortForm){return function(date,formats){var value=date["get"+name](),get=uppercase(shortForm?"SHORT"+name:name);return formats[get][value]}}function timeZoneGetter(date,formats,offset){var zone=-1*offset,paddedZone=zone>=0?"+":"";return paddedZone+=padNumber(Math[zone>0?"floor":"ceil"](zone/60),2)+padNumber(Math.abs(zone%60),2)}function getFirstThursdayOfYear(year){var dayOfWeekOnFirst=new Date(year,0,1).getDay();return new Date(year,0,(4>=dayOfWeekOnFirst?5:12)-dayOfWeekOnFirst)}function getThursdayThisWeek(datetime){return new Date(datetime.getFullYear(),datetime.getMonth(),datetime.getDate()+(4-datetime.getDay()))}function weekGetter(size){return function(date){var firstThurs=getFirstThursdayOfYear(date.getFullYear()),thisThurs=getThursdayThisWeek(date),diff=+thisThurs-+firstThurs,result=1+Math.round(diff/6048e5);return padNumber(result,size)}}function ampmGetter(date,formats){return date.getHours()<12?formats.AMPMS[0]:formats.AMPMS[1]}function eraGetter(date,formats){return date.getFullYear()<=0?formats.ERAS[0]:formats.ERAS[1]}function longEraGetter(date,formats){return date.getFullYear()<=0?formats.ERANAMES[0]:formats.ERANAMES[1]}function dateFilter($locale){function jsonStringToDate(string){var match;if(match=string.match(R_ISO8601_STR)){var date=new Date(0),tzHour=0,tzMin=0,dateSetter=match[8]?date.setUTCFullYear:date.setFullYear,timeSetter=match[8]?date.setUTCHours:date.setHours;match[9]&&(tzHour=toInt(match[9]+match[10]),tzMin=toInt(match[9]+match[11])),dateSetter.call(date,toInt(match[1]),toInt(match[2])-1,toInt(match[3]));var h=toInt(match[4]||0)-tzHour,m=toInt(match[5]||0)-tzMin,s=toInt(match[6]||0),ms=Math.round(1e3*parseFloat("0."+(match[7]||0)));return timeSetter.call(date,h,m,s,ms),date}return string}var R_ISO8601_STR=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(date,format,timezone){var fn,match,text="",parts=[];if(format=format||"mediumDate",format=$locale.DATETIME_FORMATS[format]||format,isString(date)&&(date=NUMBER_STRING.test(date)?toInt(date):jsonStringToDate(date)),isNumber(date)&&(date=new Date(date)),!isDate(date)||!isFinite(date.getTime()))return date;for(;format;)match=DATE_FORMATS_SPLIT.exec(format),match?(parts=concat(parts,match,1),format=parts.pop()):(parts.push(format),format=null);var dateTimezoneOffset=date.getTimezoneOffset();return timezone&&(dateTimezoneOffset=timezoneToOffset(timezone,date.getTimezoneOffset()),date=convertTimezoneToLocal(date,timezone,!0)),forEach(parts,function(value){fn=DATE_FORMATS[value],text+=fn?fn(date,$locale.DATETIME_FORMATS,dateTimezoneOffset):value.replace(/(^'|'$)/g,"").replace(/''/g,"'")}),text}}function jsonFilter(){return function(object,spacing){return isUndefined(spacing)&&(spacing=2),toJson(object,spacing)}}function limitToFilter(){return function(input,limit,begin){return limit=Math.abs(Number(limit))===1/0?Number(limit):toInt(limit),isNaN(limit)?input:(isNumber(input)&&(input=input.toString()),isArray(input)||isString(input)?(begin=!begin||isNaN(begin)?0:toInt(begin),begin=0>begin&&begin>=-input.length?input.length+begin:begin,limit>=0?input.slice(begin,begin+limit):0===begin?input.slice(limit,input.length):input.slice(Math.max(0,begin+limit),begin)):input)}}function orderByFilter($parse){function processPredicates(sortPredicate,reverseOrder){return reverseOrder=reverseOrder?-1:1,sortPredicate.map(function(predicate){var descending=1,get=identity;if(isFunction(predicate))get=predicate;else if(isString(predicate)&&(("+"==predicate.charAt(0)||"-"==predicate.charAt(0))&&(descending="-"==predicate.charAt(0)?-1:1,predicate=predicate.substring(1)),""!==predicate&&(get=$parse(predicate),get.constant))){var key=get();get=function(value){return value[key]}}return{get:get,descending:descending*reverseOrder}})}function isPrimitive(value){switch(typeof value){case"number":case"boolean":case"string":return!0;default:return!1}}function objectValue(value,index){return"function"==typeof value.valueOf&&(value=value.valueOf(),isPrimitive(value))?value:hasCustomToString(value)&&(value=value.toString(),isPrimitive(value))?value:index}function getPredicateValue(value,index){var type=typeof value;return null===value?(type="string",value="null"):"string"===type?value=value.toLowerCase():"object"===type&&(value=objectValue(value,index)),{value:value,type:type}}function compare(v1,v2){var result=0;return v1.type===v2.type?v1.value!==v2.value&&(result=v1.valueindex&&!(result=compare(v1.predicateValues[index],v2.predicateValues[index])*predicates[index].descending);++index);return result}if(!isArrayLike(array))return array;isArray(sortPredicate)||(sortPredicate=[sortPredicate]),0===sortPredicate.length&&(sortPredicate=["+"]);var predicates=processPredicates(sortPredicate,reverseOrder);predicates.push({get:function(){return{}},descending:reverseOrder?-1:1});var compareValues=Array.prototype.map.call(array,getComparisonObject);return compareValues.sort(doComparison),array=compareValues.map(function(item){return item.value})}}function ngDirective(directive){return isFunction(directive)&&(directive={link:directive}),directive.restrict=directive.restrict||"AC",valueFn(directive)}function nullFormRenameControl(control,name){control.$name=name}function FormController(element,attrs,$scope,$animate,$interpolate){var form=this,controls=[],parentForm=form.$$parentForm=element.parent().controller("form")||nullFormCtrl;form.$error={},form.$$success={},form.$pending=undefined,form.$name=$interpolate(attrs.name||attrs.ngForm||"")($scope),form.$dirty=!1,form.$pristine=!0,form.$valid=!0,form.$invalid=!1,form.$submitted=!1,parentForm.$addControl(form),form.$rollbackViewValue=function(){forEach(controls,function(control){control.$rollbackViewValue()})},form.$commitViewValue=function(){forEach(controls,function(control){control.$commitViewValue()})},form.$addControl=function(control){assertNotHasOwnProperty(control.$name,"input"),controls.push(control),control.$name&&(form[control.$name]=control)},form.$$renameControl=function(control,newName){var oldName=control.$name;form[oldName]===control&&delete form[oldName],form[newName]=control,control.$name=newName},form.$removeControl=function(control){control.$name&&form[control.$name]===control&&delete form[control.$name],forEach(form.$pending,function(value,name){form.$setValidity(name,null,control)}),forEach(form.$error,function(value,name){form.$setValidity(name,null,control)}),forEach(form.$$success,function(value,name){form.$setValidity(name,null,control)}),arrayRemove(controls,control)},addSetValidityMethod({ctrl:this,$element:element,set:function(object,property,controller){var list=object[property];if(list){ +var index=list.indexOf(controller);-1===index&&list.push(controller)}else object[property]=[controller]},unset:function(object,property,controller){var list=object[property];list&&(arrayRemove(list,controller),0===list.length&&delete object[property])},parentForm:parentForm,$animate:$animate}),form.$setDirty=function(){$animate.removeClass(element,PRISTINE_CLASS),$animate.addClass(element,DIRTY_CLASS),form.$dirty=!0,form.$pristine=!1,parentForm.$setDirty()},form.$setPristine=function(){$animate.setClass(element,PRISTINE_CLASS,DIRTY_CLASS+" "+SUBMITTED_CLASS),form.$dirty=!1,form.$pristine=!0,form.$submitted=!1,forEach(controls,function(control){control.$setPristine()})},form.$setUntouched=function(){forEach(controls,function(control){control.$setUntouched()})},form.$setSubmitted=function(){$animate.addClass(element,SUBMITTED_CLASS),form.$submitted=!0,parentForm.$setSubmitted()}}function stringBasedInputType(ctrl){ctrl.$formatters.push(function(value){return ctrl.$isEmpty(value)?value:value.toString()})}function textInputType(scope,element,attr,ctrl,$sniffer,$browser){baseInputType(scope,element,attr,ctrl,$sniffer,$browser),stringBasedInputType(ctrl)}function baseInputType(scope,element,attr,ctrl,$sniffer,$browser){var type=lowercase(element[0].type);if(!$sniffer.android){var composing=!1;element.on("compositionstart",function(data){composing=!0}),element.on("compositionend",function(){composing=!1,listener()})}var listener=function(ev){if(timeout&&($browser.defer.cancel(timeout),timeout=null),!composing){var value=element.val(),event=ev&&ev.type;"password"===type||attr.ngTrim&&"false"===attr.ngTrim||(value=trim(value)),(ctrl.$viewValue!==value||""===value&&ctrl.$$hasNativeValidators)&&ctrl.$setViewValue(value,event)}};if($sniffer.hasEvent("input"))element.on("input",listener);else{var timeout,deferListener=function(ev,input,origValue){timeout||(timeout=$browser.defer(function(){timeout=null,input&&input.value===origValue||listener(ev)}))};element.on("keydown",function(event){var key=event.keyCode;91===key||key>15&&19>key||key>=37&&40>=key||deferListener(event,this,this.value)}),$sniffer.hasEvent("paste")&&element.on("paste cut",deferListener)}element.on("change",listener),ctrl.$render=function(){var value=ctrl.$isEmpty(ctrl.$viewValue)?"":ctrl.$viewValue;element.val()!==value&&element.val(value)}}function weekParser(isoWeek,existingDate){if(isDate(isoWeek))return isoWeek;if(isString(isoWeek)){WEEK_REGEXP.lastIndex=0;var parts=WEEK_REGEXP.exec(isoWeek);if(parts){var year=+parts[1],week=+parts[2],hours=0,minutes=0,seconds=0,milliseconds=0,firstThurs=getFirstThursdayOfYear(year),addDays=7*(week-1);return existingDate&&(hours=existingDate.getHours(),minutes=existingDate.getMinutes(),seconds=existingDate.getSeconds(),milliseconds=existingDate.getMilliseconds()),new Date(year,0,firstThurs.getDate()+addDays,hours,minutes,seconds,milliseconds)}}return NaN}function createDateParser(regexp,mapping){return function(iso,date){var parts,map;if(isDate(iso))return iso;if(isString(iso)){if('"'==iso.charAt(0)&&'"'==iso.charAt(iso.length-1)&&(iso=iso.substring(1,iso.length-1)),ISO_DATE_REGEXP.test(iso))return new Date(iso);if(regexp.lastIndex=0,parts=regexp.exec(iso))return parts.shift(),map=date?{yyyy:date.getFullYear(),MM:date.getMonth()+1,dd:date.getDate(),HH:date.getHours(),mm:date.getMinutes(),ss:date.getSeconds(),sss:date.getMilliseconds()/1e3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},forEach(parts,function(part,index){index=minVal},attr.$observe("min",function(val){minVal=parseObservedDateValue(val),ctrl.$validate()})}if(isDefined(attr.max)||attr.ngMax){var maxVal;ctrl.$validators.max=function(value){return!isValidDate(value)||isUndefined(maxVal)||parseDate(value)<=maxVal},attr.$observe("max",function(val){maxVal=parseObservedDateValue(val),ctrl.$validate()})}}}function badInputChecker(scope,element,attr,ctrl){var node=element[0],nativeValidation=ctrl.$$hasNativeValidators=isObject(node.validity);nativeValidation&&ctrl.$parsers.push(function(value){var validity=element.prop(VALIDITY_STATE_PROPERTY)||{};return validity.badInput&&!validity.typeMismatch?undefined:value})}function numberInputType(scope,element,attr,ctrl,$sniffer,$browser){if(badInputChecker(scope,element,attr,ctrl),baseInputType(scope,element,attr,ctrl,$sniffer,$browser),ctrl.$$parserName="number",ctrl.$parsers.push(function(value){return ctrl.$isEmpty(value)?null:NUMBER_REGEXP.test(value)?parseFloat(value):undefined}),ctrl.$formatters.push(function(value){if(!ctrl.$isEmpty(value)){if(!isNumber(value))throw ngModelMinErr("numfmt","Expected `{0}` to be a number",value);value=value.toString()}return value}),isDefined(attr.min)||attr.ngMin){var minVal;ctrl.$validators.min=function(value){return ctrl.$isEmpty(value)||isUndefined(minVal)||value>=minVal},attr.$observe("min",function(val){isDefined(val)&&!isNumber(val)&&(val=parseFloat(val,10)),minVal=isNumber(val)&&!isNaN(val)?val:undefined,ctrl.$validate()})}if(isDefined(attr.max)||attr.ngMax){var maxVal;ctrl.$validators.max=function(value){return ctrl.$isEmpty(value)||isUndefined(maxVal)||maxVal>=value},attr.$observe("max",function(val){isDefined(val)&&!isNumber(val)&&(val=parseFloat(val,10)),maxVal=isNumber(val)&&!isNaN(val)?val:undefined,ctrl.$validate()})}}function urlInputType(scope,element,attr,ctrl,$sniffer,$browser){baseInputType(scope,element,attr,ctrl,$sniffer,$browser),stringBasedInputType(ctrl),ctrl.$$parserName="url",ctrl.$validators.url=function(modelValue,viewValue){var value=modelValue||viewValue;return ctrl.$isEmpty(value)||URL_REGEXP.test(value)}}function emailInputType(scope,element,attr,ctrl,$sniffer,$browser){baseInputType(scope,element,attr,ctrl,$sniffer,$browser),stringBasedInputType(ctrl),ctrl.$$parserName="email",ctrl.$validators.email=function(modelValue,viewValue){var value=modelValue||viewValue;return ctrl.$isEmpty(value)||EMAIL_REGEXP.test(value)}}function radioInputType(scope,element,attr,ctrl){isUndefined(attr.name)&&element.attr("name",nextUid());var listener=function(ev){element[0].checked&&ctrl.$setViewValue(attr.value,ev&&ev.type)};element.on("click",listener),ctrl.$render=function(){var value=attr.value;element[0].checked=value==ctrl.$viewValue},attr.$observe("value",ctrl.$render)}function parseConstantExpr($parse,context,name,expression,fallback){var parseFn;if(isDefined(expression)){if(parseFn=$parse(expression),!parseFn.constant)throw ngModelMinErr("constexpr","Expected constant expression for `{0}`, but saw `{1}`.",name,expression);return parseFn(context)}return fallback}function checkboxInputType(scope,element,attr,ctrl,$sniffer,$browser,$filter,$parse){var trueValue=parseConstantExpr($parse,scope,"ngTrueValue",attr.ngTrueValue,!0),falseValue=parseConstantExpr($parse,scope,"ngFalseValue",attr.ngFalseValue,!1),listener=function(ev){ctrl.$setViewValue(element[0].checked,ev&&ev.type)};element.on("click",listener),ctrl.$render=function(){element[0].checked=ctrl.$viewValue},ctrl.$isEmpty=function(value){return value===!1},ctrl.$formatters.push(function(value){return equals(value,trueValue)}),ctrl.$parsers.push(function(value){return value?trueValue:falseValue})}function classDirective(name,selector){return name="ngClass"+name,["$animate",function($animate){function arrayDifference(tokens1,tokens2){var values=[];outer:for(var i=0;i0||classCounts[className])&&(classCounts[className]=(classCounts[className]||0)+count,classCounts[className]===+(count>0)&&classesToUpdate.push(className))}),element.data("$classCounts",classCounts),classesToUpdate.join(" ")}function updateClasses(oldClasses,newClasses){var toAdd=arrayDifference(newClasses,oldClasses),toRemove=arrayDifference(oldClasses,newClasses);toAdd=digestClassCounts(toAdd,1),toRemove=digestClassCounts(toRemove,-1),toAdd&&toAdd.length&&$animate.addClass(element,toAdd),toRemove&&toRemove.length&&$animate.removeClass(element,toRemove)}function ngClassWatchAction(newVal){if(selector===!0||scope.$index%2===selector){var newClasses=arrayClasses(newVal||[]);if(oldVal){if(!equals(newVal,oldVal)){var oldClasses=arrayClasses(oldVal);updateClasses(oldClasses,newClasses)}}else addClasses(newClasses)}oldVal=shallowCopy(newVal)}var oldVal;scope.$watch(attr[name],ngClassWatchAction,!0),attr.$observe("class",function(value){ngClassWatchAction(scope.$eval(attr[name]))}),"ngClass"!==name&&scope.$watch("$index",function($index,old$index){var mod=1&$index;if(mod!==(1&old$index)){var classes=arrayClasses(scope.$eval(attr[name]));mod===selector?addClasses(classes):removeClasses(classes)}})}}}]}function addSetValidityMethod(context){function setValidity(validationErrorKey,state,controller){state===undefined?createAndSet("$pending",validationErrorKey,controller):unsetAndCleanup("$pending",validationErrorKey,controller),isBoolean(state)?state?(unset(ctrl.$error,validationErrorKey,controller),set(ctrl.$$success,validationErrorKey,controller)):(set(ctrl.$error,validationErrorKey,controller),unset(ctrl.$$success,validationErrorKey,controller)):(unset(ctrl.$error,validationErrorKey,controller),unset(ctrl.$$success,validationErrorKey,controller)),ctrl.$pending?(cachedToggleClass(PENDING_CLASS,!0),ctrl.$valid=ctrl.$invalid=undefined,toggleValidationCss("",null)):(cachedToggleClass(PENDING_CLASS,!1),ctrl.$valid=isObjectEmpty(ctrl.$error),ctrl.$invalid=!ctrl.$valid,toggleValidationCss("",ctrl.$valid));var combinedState;combinedState=ctrl.$pending&&ctrl.$pending[validationErrorKey]?undefined:ctrl.$error[validationErrorKey]?!1:ctrl.$$success[validationErrorKey]?!0:null,toggleValidationCss(validationErrorKey,combinedState),parentForm.$setValidity(validationErrorKey,combinedState,ctrl)}function createAndSet(name,value,controller){ctrl[name]||(ctrl[name]={}),set(ctrl[name],value,controller)}function unsetAndCleanup(name,value,controller){ctrl[name]&&unset(ctrl[name],value,controller),isObjectEmpty(ctrl[name])&&(ctrl[name]=undefined)}function cachedToggleClass(className,switchValue){switchValue&&!classCache[className]?($animate.addClass($element,className),classCache[className]=!0):!switchValue&&classCache[className]&&($animate.removeClass($element,className),classCache[className]=!1)}function toggleValidationCss(validationErrorKey,isValid){validationErrorKey=validationErrorKey?"-"+snake_case(validationErrorKey,"-"):"",cachedToggleClass(VALID_CLASS+validationErrorKey,isValid===!0),cachedToggleClass(INVALID_CLASS+validationErrorKey,isValid===!1)}var ctrl=context.ctrl,$element=context.$element,classCache={},set=context.set,unset=context.unset,parentForm=context.parentForm,$animate=context.$animate;classCache[INVALID_CLASS]=!(classCache[VALID_CLASS]=$element.hasClass(VALID_CLASS)),ctrl.$setValidity=setValidity}function isObjectEmpty(obj){if(obj)for(var prop in obj)if(obj.hasOwnProperty(prop))return!1;return!0}var REGEX_STRING_REGEXP=/^\/(.+)\/([a-z]*)$/,VALIDITY_STATE_PROPERTY="validity",lowercase=function(string){return isString(string)?string.toLowerCase():string},hasOwnProperty=Object.prototype.hasOwnProperty,uppercase=function(string){return isString(string)?string.toUpperCase():string},manualLowercase=function(s){return isString(s)?s.replace(/[A-Z]/g,function(ch){return String.fromCharCode(32|ch.charCodeAt(0))}):s},manualUppercase=function(s){return isString(s)?s.replace(/[a-z]/g,function(ch){return String.fromCharCode(-33&ch.charCodeAt(0))}):s};"i"!=="I".toLowerCase()&&(lowercase=manualLowercase,uppercase=manualUppercase);var msie,jqLite,jQuery,angularModule,slice=[].slice,splice=[].splice,push=[].push,toString=Object.prototype.toString,getPrototypeOf=Object.getPrototypeOf,ngMinErr=minErr("ng"),angular=window.angular||(window.angular={}),uid=0;msie=document.documentMode,noop.$inject=[],identity.$inject=[];var skipDestroyOnNextJQueryCleanData,isArray=Array.isArray,TYPED_ARRAY_REGEXP=/^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/,trim=function(value){return isString(value)?value.trim():value},escapeForRegexp=function(s){return s.replace(/([-()\[\]{}+?*.$\^|,:#i;++i)if(prefix=ngAttrPrefixes[i],el=document.querySelector("["+prefix.replace(":","\\:")+"jq]")){name=el.getAttribute(prefix+"jq");break}return jq.name_=name},ngAttrPrefixes=["ng-","data-ng-","ng:","x-ng-"],SNAKE_CASE_REGEXP=/[A-Z]/g,bindJQueryFired=!1,NODE_TYPE_ELEMENT=1,NODE_TYPE_ATTRIBUTE=2,NODE_TYPE_TEXT=3,NODE_TYPE_COMMENT=8,NODE_TYPE_DOCUMENT=9,NODE_TYPE_DOCUMENT_FRAGMENT=11,version={full:"1.4.4",major:1,minor:4,dot:4,codeName:"pylon-requirement"};JQLite.expando="ng339";var jqCache=JQLite.cache={},jqId=1,addEventListenerFn=function(element,type,fn){element.addEventListener(type,fn,!1)},removeEventListenerFn=function(element,type,fn){element.removeEventListener(type,fn,!1)};JQLite._data=function(node){return this.cache[node[this.expando]]||{}};var SPECIAL_CHARS_REGEXP=/([\:\-\_]+(.))/g,MOZ_HACK_REGEXP=/^moz([A-Z])/,MOUSE_EVENT_MAP={mouseleave:"mouseout",mouseenter:"mouseover"},jqLiteMinErr=minErr("jqLite"),SINGLE_TAG_REGEXP=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,HTML_REGEXP=/<|&#?\w+;/,TAG_NAME_REGEXP=/<([\w:]+)/,XHTML_TAG_REGEXP=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,wrapMap={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};wrapMap.optgroup=wrapMap.option,wrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead,wrapMap.th=wrapMap.td;var JQLitePrototype=JQLite.prototype={ready:function(fn){function trigger(){fired||(fired=!0,fn())}var fired=!1;"complete"===document.readyState?setTimeout(trigger):(this.on("DOMContentLoaded",trigger),JQLite(window).on("load",trigger))},toString:function(){var value=[];return forEach(this,function(e){value.push(""+e)}),"["+value.join(", ")+"]"},eq:function(index){return jqLite(index>=0?this[index]:this[this.length+index])},length:0,push:push,sort:[].sort,splice:[].splice},BOOLEAN_ATTR={};forEach("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(value){BOOLEAN_ATTR[lowercase(value)]=value});var BOOLEAN_ELEMENTS={};forEach("input,select,option,textarea,button,form,details".split(","),function(value){BOOLEAN_ELEMENTS[value]=!0});var ALIASED_ATTR={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};forEach({data:jqLiteData,removeData:jqLiteRemoveData,hasData:jqLiteHasData},function(fn,name){JQLite[name]=fn}),forEach({data:jqLiteData,inheritedData:jqLiteInheritedData,scope:function(element){return jqLite.data(element,"$scope")||jqLiteInheritedData(element.parentNode||element,["$isolateScope","$scope"])},isolateScope:function(element){return jqLite.data(element,"$isolateScope")||jqLite.data(element,"$isolateScopeNoTemplate")},controller:jqLiteController,injector:function(element){return jqLiteInheritedData(element,"$injector")},removeAttr:function(element,name){element.removeAttribute(name)},hasClass:jqLiteHasClass,css:function(element,name,value){return name=camelCase(name),isDefined(value)?void(element.style[name]=value):element.style[name]},attr:function(element,name,value){var nodeType=element.nodeType;if(nodeType!==NODE_TYPE_TEXT&&nodeType!==NODE_TYPE_ATTRIBUTE&&nodeType!==NODE_TYPE_COMMENT){var lowercasedName=lowercase(name);if(BOOLEAN_ATTR[lowercasedName]){if(!isDefined(value))return element[name]||(element.attributes.getNamedItem(name)||noop).specified?lowercasedName:undefined;value?(element[name]=!0,element.setAttribute(name,lowercasedName)):(element[name]=!1,element.removeAttribute(lowercasedName))}else if(isDefined(value))element.setAttribute(name,value);else if(element.getAttribute){var ret=element.getAttribute(name,2);return null===ret?undefined:ret}}},prop:function(element,name,value){return isDefined(value)?void(element[name]=value):element[name]},text:function(){function getText(element,value){if(isUndefined(value)){var nodeType=element.nodeType;return nodeType===NODE_TYPE_ELEMENT||nodeType===NODE_TYPE_TEXT?element.textContent:""}element.textContent=value}return getText.$dv="",getText}(),val:function(element,value){if(isUndefined(value)){if(element.multiple&&"select"===nodeName_(element)){var result=[];return forEach(element.options,function(option){option.selected&&result.push(option.value||option.text)}),0===result.length?null:result}return element.value}element.value=value},html:function(element,value){return isUndefined(value)?element.innerHTML:(jqLiteDealoc(element,!0),void(element.innerHTML=value))},empty:jqLiteEmpty},function(fn,name){JQLite.prototype[name]=function(arg1,arg2){var i,key,nodeCount=this.length;if(fn!==jqLiteEmpty&&(2==fn.length&&fn!==jqLiteHasClass&&fn!==jqLiteController?arg1:arg2)===undefined){if(isObject(arg1)){for(i=0;nodeCount>i;i++)if(fn===jqLiteData)fn(this[i],arg1);else for(key in arg1)fn(this[i],key,arg1[key]);return this}for(var value=fn.$dv,jj=value===undefined?Math.min(nodeCount,1):nodeCount,j=0;jj>j;j++){var nodeValue=fn(this[j],arg1,arg2);value=value?value+nodeValue:nodeValue}return value}for(i=0;nodeCount>i;i++)fn(this[i],arg1,arg2);return this}}),forEach({removeData:jqLiteRemoveData,on:function jqLiteOn(element,type,fn,unsupported){if(isDefined(unsupported))throw jqLiteMinErr("onargs","jqLite#on() does not support the `selector` or `eventData` parameters");if(jqLiteAcceptsData(element)){var expandoStore=jqLiteExpandoStore(element,!0),events=expandoStore.events,handle=expandoStore.handle;handle||(handle=expandoStore.handle=createEventHandler(element,events));for(var types=type.indexOf(" ")>=0?type.split(" "):[type],i=types.length;i--;){type=types[i];var eventFns=events[type];eventFns||(events[type]=[],"mouseenter"===type||"mouseleave"===type?jqLiteOn(element,MOUSE_EVENT_MAP[type],function(event){var target=this,related=event.relatedTarget;(!related||related!==target&&!target.contains(related))&&handle(event,type)}):"$destroy"!==type&&addEventListenerFn(element,type,handle),eventFns=events[type]),eventFns.push(fn)}}},off:jqLiteOff,one:function(element,type,fn){element=jqLite(element),element.on(type,function onFn(){element.off(type,fn),element.off(type,onFn)}),element.on(type,fn)},replaceWith:function(element,replaceNode){var index,parent=element.parentNode;jqLiteDealoc(element),forEach(new JQLite(replaceNode),function(node){index?parent.insertBefore(node,index.nextSibling):parent.replaceChild(node,element),index=node})},children:function(element){var children=[];return forEach(element.childNodes,function(element){element.nodeType===NODE_TYPE_ELEMENT&&children.push(element)}),children},contents:function(element){return element.contentDocument||element.childNodes||[]},append:function(element,node){var nodeType=element.nodeType;if(nodeType===NODE_TYPE_ELEMENT||nodeType===NODE_TYPE_DOCUMENT_FRAGMENT){node=new JQLite(node);for(var i=0,ii=node.length;ii>i;i++){var child=node[i];element.appendChild(child)}}},prepend:function(element,node){if(element.nodeType===NODE_TYPE_ELEMENT){var index=element.firstChild;forEach(new JQLite(node),function(child){element.insertBefore(child,index)})}},wrap:function(element,wrapNode){wrapNode=jqLite(wrapNode).eq(0).clone()[0];var parent=element.parentNode;parent&&parent.replaceChild(wrapNode,element),wrapNode.appendChild(element)},remove:jqLiteRemove,detach:function(element){jqLiteRemove(element,!0)},after:function(element,newElement){var index=element,parent=element.parentNode;newElement=new JQLite(newElement);for(var i=0,ii=newElement.length;ii>i;i++){var node=newElement[i];parent.insertBefore(node,index.nextSibling),index=node}},addClass:jqLiteAddClass,removeClass:jqLiteRemoveClass,toggleClass:function(element,selector,condition){selector&&forEach(selector.split(" "),function(className){var classCondition=condition;isUndefined(classCondition)&&(classCondition=!jqLiteHasClass(element,className)),(classCondition?jqLiteAddClass:jqLiteRemoveClass)(element,className)})},parent:function(element){var parent=element.parentNode;return parent&&parent.nodeType!==NODE_TYPE_DOCUMENT_FRAGMENT?parent:null},next:function(element){return element.nextElementSibling},find:function(element,selector){return element.getElementsByTagName?element.getElementsByTagName(selector):[]},clone:jqLiteClone,triggerHandler:function(element,event,extraParameters){var dummyEvent,eventFnsCopy,handlerArgs,eventName=event.type||event,expandoStore=jqLiteExpandoStore(element),events=expandoStore&&expandoStore.events,eventFns=events&&events[eventName];eventFns&&(dummyEvent={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return this.defaultPrevented===!0},stopImmediatePropagation:function(){this.immediatePropagationStopped=!0},isImmediatePropagationStopped:function(){return this.immediatePropagationStopped===!0},stopPropagation:noop,type:eventName,target:element},event.type&&(dummyEvent=extend(dummyEvent,event)),eventFnsCopy=shallowCopy(eventFns),handlerArgs=extraParameters?[dummyEvent].concat(extraParameters):[dummyEvent],forEach(eventFnsCopy,function(fn){dummyEvent.isImmediatePropagationStopped()||fn.apply(element,handlerArgs)}))}},function(fn,name){JQLite.prototype[name]=function(arg1,arg2,arg3){for(var value,i=0,ii=this.length;ii>i;i++)isUndefined(value)?(value=fn(this[i],arg1,arg2,arg3),isDefined(value)&&(value=jqLite(value))):jqLiteAddNodes(value,fn(this[i],arg1,arg2,arg3));return isDefined(value)?value:this},JQLite.prototype.bind=JQLite.prototype.on,JQLite.prototype.unbind=JQLite.prototype.off}),HashMap.prototype={put:function(key,value){this[hashKey(key,this.nextUid)]=value},get:function(key){return this[hashKey(key,this.nextUid)]},remove:function(key){var value=this[key=hashKey(key,this.nextUid)];return delete this[key],value}};var $$HashMapProvider=[function(){this.$get=[function(){return HashMap}]}],FN_ARGS=/^[^\(]*\(\s*([^\)]*)\)/m,FN_ARG_SPLIT=/,/,FN_ARG=/^\s*(_?)(\S+?)\1\s*$/,STRIP_COMMENTS=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,$injectorMinErr=minErr("$injector");createInjector.$$annotate=annotate;var $animateMinErr=minErr("$animate"),ELEMENT_NODE=1,NG_ANIMATE_CLASSNAME="ng-animate",$$CoreAnimateRunnerProvider=function(){this.$get=["$q","$$rAF",function($q,$$rAF){function AnimateRunner(){}return AnimateRunner.all=noop,AnimateRunner.chain=noop,AnimateRunner.prototype={end:noop,cancel:noop,resume:noop,pause:noop,complete:noop,then:function(pass,fail){return $q(function(resolve){$$rAF(function(){resolve()})}).then(pass,fail)}},AnimateRunner}]},$$CoreAnimateQueueProvider=function(){var postDigestQueue=new HashMap,postDigestElements=[];this.$get=["$$AnimateRunner","$rootScope",function($$AnimateRunner,$rootScope){function addRemoveClassesPostDigest(element,add,remove){var data=postDigestQueue.get(element);data||(postDigestQueue.put(element,data={}),postDigestElements.push(element));var updateData=function(classes,value){var changed=!1;return classes&&(classes=isString(classes)?classes.split(" "):isArray(classes)?classes:[],forEach(classes,function(className){className&&(changed=!0,data[className]=value)})),changed},classesAdded=updateData(add,!0),classesRemoved=updateData(remove,!1);!classesAdded&&!classesRemoved||postDigestElements.length>1||$rootScope.$$postDigest(function(){forEach(postDigestElements,function(element){var data=postDigestQueue.get(element);if(data){var existing=splitClasses(element.attr("class")),toAdd="",toRemove="";forEach(data,function(status,className){var hasClass=!!existing[className];status!==hasClass&&(status?toAdd+=(toAdd.length?" ":"")+className:toRemove+=(toRemove.length?" ":"")+className)}),forEach(element,function(elm){toAdd&&jqLiteAddClass(elm,toAdd),toRemove&&jqLiteRemoveClass(elm,toRemove)}),postDigestQueue.remove(element)}}),postDigestElements.length=0})}return{enabled:noop,on:noop,off:noop,pin:noop,push:function(element,event,options,domOperation){return domOperation&&domOperation(),options=options||{},options.from&&element.css(options.from),options.to&&element.css(options.to),(options.addClass||options.removeClass)&&addRemoveClassesPostDigest(element,options.addClass,options.removeClass),new $$AnimateRunner}}}]},$AnimateProvider=["$provide",function($provide){var provider=this;this.$$registeredAnimations=Object.create(null),this.register=function(name,factory){if(name&&"."!==name.charAt(0))throw $animateMinErr("notcsel","Expecting class selector starting with '.' got '{0}'.",name);var key=name+"-animation";provider.$$registeredAnimations[name.substr(1)]=key,$provide.factory(key,factory)},this.classNameFilter=function(expression){if(1===arguments.length&&(this.$$classNameFilter=expression instanceof RegExp?expression:null,this.$$classNameFilter)){var reservedRegex=new RegExp("(\\s+|\\/)"+NG_ANIMATE_CLASSNAME+"(\\s+|\\/)");if(reservedRegex.test(this.$$classNameFilter.toString()))throw $animateMinErr("nongcls",'$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.',NG_ANIMATE_CLASSNAME)}return this.$$classNameFilter},this.$get=["$$animateQueue",function($$animateQueue){function domInsert(element,parentElement,afterElement){if(afterElement){var afterNode=extractElementNode(afterElement);!afterNode||afterNode.parentNode||afterNode.previousElementSibling||(afterElement=null)}afterElement?afterElement.after(element):parentElement.prepend(element)}return{on:$$animateQueue.on,off:$$animateQueue.off,pin:$$animateQueue.pin,enabled:$$animateQueue.enabled,cancel:function(runner){runner.end&&runner.end()},enter:function(element,parent,after,options){return parent=parent&&jqLite(parent),after=after&&jqLite(after),parent=parent||after.parent(),domInsert(element,parent,after),$$animateQueue.push(element,"enter",prepareAnimateOptions(options))},move:function(element,parent,after,options){return parent=parent&&jqLite(parent),after=after&&jqLite(after),parent=parent||after.parent(),domInsert(element,parent,after),$$animateQueue.push(element,"move",prepareAnimateOptions(options))},leave:function(element,options){return $$animateQueue.push(element,"leave",prepareAnimateOptions(options),function(){element.remove()})},addClass:function(element,className,options){return options=prepareAnimateOptions(options),options.addClass=mergeClasses(options.addclass,className),$$animateQueue.push(element,"addClass",options)},removeClass:function(element,className,options){return options=prepareAnimateOptions(options),options.removeClass=mergeClasses(options.removeClass,className),$$animateQueue.push(element,"removeClass",options)},setClass:function(element,add,remove,options){return options=prepareAnimateOptions(options),options.addClass=mergeClasses(options.addClass,add),options.removeClass=mergeClasses(options.removeClass,remove),$$animateQueue.push(element,"setClass",options)},animate:function(element,from,to,className,options){return options=prepareAnimateOptions(options),options.from=options.from?extend(options.from,from):from,options.to=options.to?extend(options.to,to):to,className=className||"ng-inline-animate",options.tempClasses=mergeClasses(options.tempClasses,className),$$animateQueue.push(element,"animate",options)}}}]}],$CoreAnimateCssProvider=function(){this.$get=["$$rAF","$q",function($$rAF,$q){var RAFPromise=function(){};return RAFPromise.prototype={done:function(cancel){this.defer&&this.defer[cancel===!0?"reject":"resolve"]()},end:function(){this.done()},cancel:function(){this.done(!0)},getPromise:function(){return this.defer||(this.defer=$q.defer()),this.defer.promise},then:function(f1,f2){return this.getPromise().then(f1,f2)},"catch":function(f1){return this.getPromise()["catch"](f1)},"finally":function(f1){return this.getPromise()["finally"](f1)}},function(element,options){function run(){return $$rAF(function(){close(),closed||runner.done(),closed=!0}),runner}function close(){options.addClass&&(element.addClass(options.addClass),options.addClass=null),options.removeClass&&(element.removeClass(options.removeClass),options.removeClass=null),options.to&&(element.css(options.to),options.to=null)}options.from&&(element.css(options.from),options.from=null);var closed,runner=new RAFPromise;return{start:run,end:run}}}]},$compileMinErr=minErr("$compile");$CompileProvider.$inject=["$provide","$$sanitizeUriProvider"];var PREFIX_REGEXP=/^((?:x|data)[\:\-_])/i,$controllerMinErr=minErr("$controller"),CNTRL_REG=/^(\S+)(\s+as\s+(\w+))?$/,$$ForceReflowProvider=function(){this.$get=["$document",function($document){return function(domNode){return domNode?!domNode.nodeType&&domNode instanceof jqLite&&(domNode=domNode[0]):domNode=$document[0].body,domNode.offsetWidth+1}}]},APPLICATION_JSON="application/json",CONTENT_TYPE_APPLICATION_JSON={"Content-Type":APPLICATION_JSON+";charset=utf-8"},JSON_START=/^\[|^\{(?!\{)/,JSON_ENDS={"[":/]$/,"{":/}$/},JSON_PROTECTION_PREFIX=/^\)\]\}',?\n/,$httpMinErr=minErr("$http"),$httpMinErrLegacyFn=function(method){return function(){throw $httpMinErr("legacy","The method `{0}` on the promise returned from `$http` has been disabled.",method)}},$interpolateMinErr=angular.$interpolateMinErr=minErr("$interpolate");$interpolateMinErr.throwNoconcat=function(text){ +throw $interpolateMinErr("noconcat","Error while interpolating: {0}\nStrict Contextual Escaping disallows interpolations that concatenate multiple expressions when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce",text)},$interpolateMinErr.interr=function(text,err){return $interpolateMinErr("interr","Can't interpolate: {0}\n{1}",text,err.toString())};var PATH_MATCH=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,DEFAULT_PORTS={http:80,https:443,ftp:21},$locationMinErr=minErr("$location"),locationPrototype={$$html5:!1,$$replace:!1,absUrl:locationGetter("$$absUrl"),url:function(url){if(isUndefined(url))return this.$$url;var match=PATH_MATCH.exec(url);return(match[1]||""===url)&&this.path(decodeURIComponent(match[1])),(match[2]||match[1]||""===url)&&this.search(match[3]||""),this.hash(match[5]||""),this},protocol:locationGetter("$$protocol"),host:locationGetter("$$host"),port:locationGetter("$$port"),path:locationGetterSetter("$$path",function(path){return path=null!==path?path.toString():"","/"==path.charAt(0)?path:"/"+path}),search:function(search,paramValue){switch(arguments.length){case 0:return this.$$search;case 1:if(isString(search)||isNumber(search))search=search.toString(),this.$$search=parseKeyValue(search);else{if(!isObject(search))throw $locationMinErr("isrcharg","The first argument of the `$location#search()` call must be a string or an object.");search=copy(search,{}),forEach(search,function(value,key){null==value&&delete search[key]}),this.$$search=search}break;default:isUndefined(paramValue)||null===paramValue?delete this.$$search[search]:this.$$search[search]=paramValue}return this.$$compose(),this},hash:locationGetterSetter("$$hash",function(hash){return null!==hash?hash.toString():""}),replace:function(){return this.$$replace=!0,this}};forEach([LocationHashbangInHtml5Url,LocationHashbangUrl,LocationHtml5Url],function(Location){Location.prototype=Object.create(locationPrototype),Location.prototype.state=function(state){if(!arguments.length)return this.$$state;if(Location!==LocationHtml5Url||!this.$$html5)throw $locationMinErr("nostate","History API state support is available only in HTML5 mode and only in browsers supporting HTML5 History API");return this.$$state=isUndefined(state)?null:state,this}});var $parseMinErr=minErr("$parse"),CALL=Function.prototype.call,APPLY=Function.prototype.apply,BIND=Function.prototype.bind,OPERATORS=createMap();forEach("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(operator){OPERATORS[operator]=!0});var ESCAPE={n:"\n",f:"\f",r:"\r",t:" ",v:" ","'":"'",'"':'"'},Lexer=function(options){this.options=options};Lexer.prototype={constructor:Lexer,lex:function(text){for(this.text=text,this.index=0,this.tokens=[];this.index="0"&&"9">=ch&&"string"==typeof ch},isWhitespace:function(ch){return" "===ch||"\r"===ch||" "===ch||"\n"===ch||" "===ch||" "===ch},isIdent:function(ch){return ch>="a"&&"z">=ch||ch>="A"&&"Z">=ch||"_"===ch||"$"===ch},isExpOperator:function(ch){return"-"===ch||"+"===ch||this.isNumber(ch)},throwError:function(error,start,end){end=end||this.index;var colStr=isDefined(start)?"s "+start+"-"+this.index+" ["+this.text.substring(start,end)+"]":" "+end;throw $parseMinErr("lexerr","Lexer Error: {0} at column{1} in expression [{2}].",error,colStr,this.text)},readNumber:function(){for(var number="",start=this.index;this.index0&&!this.peek("}",")",";","]")&&body.push(this.expressionStatement()),!this.expect(";"))return{type:AST.Program,body:body}},expressionStatement:function(){return{type:AST.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var token,left=this.expression();token=this.expect("|");)left=this.filter(left);return left},expression:function(){return this.assignment()},assignment:function(){var result=this.ternary();return this.expect("=")&&(result={type:AST.AssignmentExpression,left:result,right:this.assignment(),operator:"="}),result},ternary:function(){var alternate,consequent,test=this.logicalOR();return this.expect("?")&&(alternate=this.expression(),this.consume(":"))?(consequent=this.expression(),{type:AST.ConditionalExpression,test:test,alternate:alternate,consequent:consequent}):test},logicalOR:function(){for(var left=this.logicalAND();this.expect("||");)left={type:AST.LogicalExpression,operator:"||",left:left,right:this.logicalAND()};return left},logicalAND:function(){for(var left=this.equality();this.expect("&&");)left={type:AST.LogicalExpression,operator:"&&",left:left,right:this.equality()};return left},equality:function(){for(var token,left=this.relational();token=this.expect("==","!=","===","!==");)left={type:AST.BinaryExpression,operator:token.text,left:left,right:this.relational()};return left},relational:function(){for(var token,left=this.additive();token=this.expect("<",">","<=",">=");)left={type:AST.BinaryExpression,operator:token.text,left:left,right:this.additive()};return left},additive:function(){for(var token,left=this.multiplicative();token=this.expect("+","-");)left={type:AST.BinaryExpression,operator:token.text,left:left,right:this.multiplicative()};return left},multiplicative:function(){for(var token,left=this.unary();token=this.expect("*","/","%");)left={type:AST.BinaryExpression,operator:token.text,left:left,right:this.unary()};return left},unary:function(){var token;return(token=this.expect("+","-","!"))?{type:AST.UnaryExpression,operator:token.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var primary;this.expect("(")?(primary=this.filterChain(),this.consume(")")):this.expect("[")?primary=this.arrayDeclaration():this.expect("{")?primary=this.object():this.constants.hasOwnProperty(this.peek().text)?primary=copy(this.constants[this.consume().text]):this.peek().identifier?primary=this.identifier():this.peek().constant?primary=this.constant():this.throwError("not a primary expression",this.peek());for(var next;next=this.expect("(","[",".");)"("===next.text?(primary={type:AST.CallExpression,callee:primary,arguments:this.parseArguments()},this.consume(")")):"["===next.text?(primary={type:AST.MemberExpression,object:primary,property:this.expression(),computed:!0},this.consume("]")):"."===next.text?primary={type:AST.MemberExpression,object:primary,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return primary},filter:function(baseExpression){for(var args=[baseExpression],result={type:AST.CallExpression,callee:this.identifier(),arguments:args,filter:!0};this.expect(":");)args.push(this.expression());return result},parseArguments:function(){var args=[];if(")"!==this.peekToken().text)do args.push(this.expression());while(this.expect(","));return args},identifier:function(){var token=this.consume();return token.identifier||this.throwError("is not a valid identifier",token),{type:AST.Identifier,name:token.text}},constant:function(){return{type:AST.Literal,value:this.consume().value}},arrayDeclaration:function(){var elements=[];if("]"!==this.peekToken().text)do{if(this.peek("]"))break;elements.push(this.expression())}while(this.expect(","));return this.consume("]"),{type:AST.ArrayExpression,elements:elements}},object:function(){var property,properties=[];if("}"!==this.peekToken().text)do{if(this.peek("}"))break;property={type:AST.Property,kind:"init"},this.peek().constant?property.key=this.constant():this.peek().identifier?property.key=this.identifier():this.throwError("invalid key",this.peek()),this.consume(":"),property.value=this.expression(),properties.push(property)}while(this.expect(","));return this.consume("}"),{type:AST.ObjectExpression,properties:properties}},throwError:function(msg,token){throw $parseMinErr("syntax","Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].",token.text,msg,token.index+1,this.text,this.text.substring(token.index))},consume:function(e1){if(0===this.tokens.length)throw $parseMinErr("ueoe","Unexpected end of expression: {0}",this.text);var token=this.expect(e1);return token||this.throwError("is unexpected, expecting ["+e1+"]",this.peek()),token},peekToken:function(){if(0===this.tokens.length)throw $parseMinErr("ueoe","Unexpected end of expression: {0}",this.text);return this.tokens[0]},peek:function(e1,e2,e3,e4){return this.peekAhead(0,e1,e2,e3,e4)},peekAhead:function(i,e1,e2,e3,e4){if(this.tokens.length>i){var token=this.tokens[i],t=token.text;if(t===e1||t===e2||t===e3||t===e4||!e1&&!e2&&!e3&&!e4)return token}return!1},expect:function(e1,e2,e3,e4){var token=this.peek(e1,e2,e3,e4);return token?(this.tokens.shift(),token):!1},constants:{"true":{type:AST.Literal,value:!0},"false":{type:AST.Literal,value:!1},"null":{type:AST.Literal,value:null},undefined:{type:AST.Literal,value:undefined},"this":{type:AST.ThisExpression}}},ASTCompiler.prototype={compile:function(expression,expensiveChecks){var self=this,ast=this.astBuilder.ast(expression);this.state={nextId:0,filters:{},expensiveChecks:expensiveChecks,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]},findConstantAndWatchExpressions(ast,self.$filter);var assignable,extra="";if(this.stage="assign",assignable=assignableAST(ast)){this.state.computing="assign";var result=this.nextId();this.recurse(assignable,result),extra="fn.assign="+this.generateFunction("assign","s,v,l")}var toWatch=getInputs(ast.body);self.stage="inputs",forEach(toWatch,function(watch,key){var fnKey="fn"+key;self.state[fnKey]={vars:[],body:[],own:{}},self.state.computing=fnKey;var intoId=self.nextId();self.recurse(watch,intoId),self.return_(intoId),self.state.inputs.push(fnKey),watch.watchId=key}),this.state.computing="fn",this.stage="main",this.recurse(ast);var fnString='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+extra+this.watchFns()+"return fn;",fn=new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","ifDefined","plus","text",fnString)(this.$filter,ensureSafeMemberName,ensureSafeObject,ensureSafeFunction,ifDefined,plusFn,expression);return this.state=this.stage=undefined,fn.literal=isLiteral(ast),fn.constant=isConstant(ast),fn},USE:"use",STRICT:"strict",watchFns:function(){var result=[],fns=this.state.inputs,self=this;return forEach(fns,function(name){result.push("var "+name+"="+self.generateFunction(name,"s"))}),fns.length&&result.push("fn.inputs=["+fns.join(",")+"];"),result.join("")},generateFunction:function(name,params){return"function("+params+"){"+this.varsPrefix(name)+this.body(name)+"};"},filterPrefix:function(){var parts=[],self=this;return forEach(this.state.filters,function(id,filter){parts.push(id+"=$filter("+self.escape(filter)+")")}),parts.length?"var "+parts.join(",")+";":""},varsPrefix:function(section){return this.state[section].vars.length?"var "+this.state[section].vars.join(",")+";":""},body:function(section){return this.state[section].body.join("")},recurse:function(ast,intoId,nameId,recursionFn,create,skipWatchIdCheck){var left,right,args,expression,self=this;if(recursionFn=recursionFn||noop,!skipWatchIdCheck&&isDefined(ast.watchId))return intoId=intoId||this.nextId(),void this.if_("i",this.lazyAssign(intoId,this.computedMember("i",ast.watchId)),this.lazyRecurse(ast,intoId,nameId,recursionFn,create,!0));switch(ast.type){case AST.Program:forEach(ast.body,function(expression,pos){self.recurse(expression.expression,undefined,undefined,function(expr){right=expr}),pos!==ast.body.length-1?self.current().body.push(right,";"):self.return_(right)});break;case AST.Literal:expression=this.escape(ast.value),this.assign(intoId,expression),recursionFn(expression);break;case AST.UnaryExpression:this.recurse(ast.argument,undefined,undefined,function(expr){right=expr}),expression=ast.operator+"("+this.ifDefined(right,0)+")",this.assign(intoId,expression),recursionFn(expression);break;case AST.BinaryExpression:this.recurse(ast.left,undefined,undefined,function(expr){left=expr}),this.recurse(ast.right,undefined,undefined,function(expr){right=expr}),expression="+"===ast.operator?this.plus(left,right):"-"===ast.operator?this.ifDefined(left,0)+ast.operator+this.ifDefined(right,0):"("+left+")"+ast.operator+"("+right+")",this.assign(intoId,expression),recursionFn(expression);break;case AST.LogicalExpression:intoId=intoId||this.nextId(),self.recurse(ast.left,intoId),self.if_("&&"===ast.operator?intoId:self.not(intoId),self.lazyRecurse(ast.right,intoId)),recursionFn(intoId);break;case AST.ConditionalExpression:intoId=intoId||this.nextId(),self.recurse(ast.test,intoId),self.if_(intoId,self.lazyRecurse(ast.alternate,intoId),self.lazyRecurse(ast.consequent,intoId)),recursionFn(intoId);break;case AST.Identifier:intoId=intoId||this.nextId(),nameId&&(nameId.context="inputs"===self.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",ast.name)+"?l:s"),nameId.computed=!1,nameId.name=ast.name),ensureSafeMemberName(ast.name),self.if_("inputs"===self.stage||self.not(self.getHasOwnProperty("l",ast.name)),function(){self.if_("inputs"===self.stage||"s",function(){create&&1!==create&&self.if_(self.not(self.nonComputedMember("s",ast.name)),self.lazyAssign(self.nonComputedMember("s",ast.name),"{}")),self.assign(intoId,self.nonComputedMember("s",ast.name))})},intoId&&self.lazyAssign(intoId,self.nonComputedMember("l",ast.name))),(self.state.expensiveChecks||isPossiblyDangerousMemberName(ast.name))&&self.addEnsureSafeObject(intoId),recursionFn(intoId);break;case AST.MemberExpression:left=nameId&&(nameId.context=this.nextId())||this.nextId(),intoId=intoId||this.nextId(),self.recurse(ast.object,left,undefined,function(){self.if_(self.notNull(left),function(){ast.computed?(right=self.nextId(),self.recurse(ast.property,right),self.addEnsureSafeMemberName(right),create&&1!==create&&self.if_(self.not(self.computedMember(left,right)),self.lazyAssign(self.computedMember(left,right),"{}")),expression=self.ensureSafeObject(self.computedMember(left,right)),self.assign(intoId,expression),nameId&&(nameId.computed=!0,nameId.name=right)):(ensureSafeMemberName(ast.property.name),create&&1!==create&&self.if_(self.not(self.nonComputedMember(left,ast.property.name)),self.lazyAssign(self.nonComputedMember(left,ast.property.name),"{}")),expression=self.nonComputedMember(left,ast.property.name),(self.state.expensiveChecks||isPossiblyDangerousMemberName(ast.property.name))&&(expression=self.ensureSafeObject(expression)),self.assign(intoId,expression),nameId&&(nameId.computed=!1,nameId.name=ast.property.name))},function(){self.assign(intoId,"undefined")}),recursionFn(intoId)},!!create);break;case AST.CallExpression:intoId=intoId||this.nextId(),ast.filter?(right=self.filter(ast.callee.name),args=[],forEach(ast.arguments,function(expr){var argument=self.nextId();self.recurse(expr,argument),args.push(argument)}),expression=right+"("+args.join(",")+")",self.assign(intoId,expression),recursionFn(intoId)):(right=self.nextId(),left={},args=[],self.recurse(ast.callee,right,left,function(){self.if_(self.notNull(right),function(){self.addEnsureSafeFunction(right),forEach(ast.arguments,function(expr){self.recurse(expr,self.nextId(),undefined,function(argument){args.push(self.ensureSafeObject(argument))})}),left.name?(self.state.expensiveChecks||self.addEnsureSafeObject(left.context),expression=self.member(left.context,left.name,left.computed)+"("+args.join(",")+")"):expression=right+"("+args.join(",")+")",expression=self.ensureSafeObject(expression),self.assign(intoId,expression)},function(){self.assign(intoId,"undefined")}),recursionFn(intoId)}));break;case AST.AssignmentExpression:if(right=this.nextId(),left={},!isAssignable(ast.left))throw $parseMinErr("lval","Trying to assing a value to a non l-value");this.recurse(ast.left,undefined,left,function(){self.if_(self.notNull(left.context),function(){self.recurse(ast.right,right),self.addEnsureSafeObject(self.member(left.context,left.name,left.computed)),expression=self.member(left.context,left.name,left.computed)+ast.operator+right,self.assign(intoId,expression),recursionFn(intoId||expression)})},1);break;case AST.ArrayExpression:args=[],forEach(ast.elements,function(expr){self.recurse(expr,self.nextId(),undefined,function(argument){args.push(argument)})}),expression="["+args.join(",")+"]",this.assign(intoId,expression),recursionFn(expression);break;case AST.ObjectExpression:args=[],forEach(ast.properties,function(property){self.recurse(property.value,self.nextId(),undefined,function(expr){args.push(self.escape(property.key.type===AST.Identifier?property.key.name:""+property.key.value)+":"+expr)})}),expression="{"+args.join(",")+"}",this.assign(intoId,expression),recursionFn(expression);break;case AST.ThisExpression:this.assign(intoId,"s"),recursionFn("s");break;case AST.NGValueParameter:this.assign(intoId,"v"),recursionFn("v")}},getHasOwnProperty:function(element,property){var key=element+"."+property,own=this.current().own;return own.hasOwnProperty(key)||(own[key]=this.nextId(!1,element+"&&("+this.escape(property)+" in "+element+")")),own[key]},assign:function(id,value){return id?(this.current().body.push(id,"=",value,";"),id):void 0},filter:function(filterName){return this.state.filters.hasOwnProperty(filterName)||(this.state.filters[filterName]=this.nextId(!0)),this.state.filters[filterName]},ifDefined:function(id,defaultValue){return"ifDefined("+id+","+this.escape(defaultValue)+")"},plus:function(left,right){return"plus("+left+","+right+")"},return_:function(id){this.current().body.push("return ",id,";")},if_:function(test,alternate,consequent){if(test===!0)alternate();else{var body=this.current().body;body.push("if(",test,"){"),alternate(),body.push("}"),consequent&&(body.push("else{"),consequent(),body.push("}"))}},not:function(expression){return"!("+expression+")"},notNull:function(expression){return expression+"!=null"},nonComputedMember:function(left,right){return left+"."+right},computedMember:function(left,right){return left+"["+right+"]"},member:function(left,right,computed){return computed?this.computedMember(left,right):this.nonComputedMember(left,right)},addEnsureSafeObject:function(item){this.current().body.push(this.ensureSafeObject(item),";")},addEnsureSafeMemberName:function(item){this.current().body.push(this.ensureSafeMemberName(item),";")},addEnsureSafeFunction:function(item){this.current().body.push(this.ensureSafeFunction(item),";")},ensureSafeObject:function(item){return"ensureSafeObject("+item+",text)"},ensureSafeMemberName:function(item){return"ensureSafeMemberName("+item+",text)"},ensureSafeFunction:function(item){return"ensureSafeFunction("+item+",text)"},lazyRecurse:function(ast,intoId,nameId,recursionFn,create,skipWatchIdCheck){var self=this;return function(){self.recurse(ast,intoId,nameId,recursionFn,create,skipWatchIdCheck)}},lazyAssign:function(id,value){var self=this;return function(){self.assign(id,value)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(c){return"\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4)},escape:function(value){if(isString(value))return"'"+value.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(isNumber(value))return value.toString();if(value===!0)return"true";if(value===!1)return"false";if(null===value)return"null";if("undefined"==typeof value)return"undefined";throw $parseMinErr("esc","IMPOSSIBLE")},nextId:function(skip,init){var id="v"+this.state.nextId++;return skip||this.current().vars.push(id+(init?"="+init:"")),id},current:function(){return this.state[this.state.computing]}},ASTInterpreter.prototype={compile:function(expression,expensiveChecks){var self=this,ast=this.astBuilder.ast(expression);this.expression=expression,this.expensiveChecks=expensiveChecks,findConstantAndWatchExpressions(ast,self.$filter);var assignable,assign;(assignable=assignableAST(ast))&&(assign=this.recurse(assignable));var inputs,toWatch=getInputs(ast.body);toWatch&&(inputs=[],forEach(toWatch,function(watch,key){var input=self.recurse(watch);watch.input=input,inputs.push(input),watch.watchId=key}));var expressions=[];forEach(ast.body,function(expression){expressions.push(self.recurse(expression.expression))});var fn=0===ast.body.length?function(){}:1===ast.body.length?expressions[0]:function(scope,locals){var lastValue;return forEach(expressions,function(exp){lastValue=exp(scope,locals)}),lastValue};return assign&&(fn.assign=function(scope,value,locals){return assign(scope,locals,value)}),inputs&&(fn.inputs=inputs),fn.literal=isLiteral(ast),fn.constant=isConstant(ast),fn},recurse:function(ast,context,create){var left,right,args,self=this;if(ast.input)return this.inputs(ast.input,ast.watchId);switch(ast.type){case AST.Literal:return this.value(ast.value,context);case AST.UnaryExpression:return right=this.recurse(ast.argument),this["unary"+ast.operator](right,context);case AST.BinaryExpression:return left=this.recurse(ast.left),right=this.recurse(ast.right),this["binary"+ast.operator](left,right,context);case AST.LogicalExpression:return left=this.recurse(ast.left),right=this.recurse(ast.right),this["binary"+ast.operator](left,right,context);case AST.ConditionalExpression:return this["ternary?:"](this.recurse(ast.test),this.recurse(ast.alternate),this.recurse(ast.consequent),context);case AST.Identifier:return ensureSafeMemberName(ast.name,self.expression),self.identifier(ast.name,self.expensiveChecks||isPossiblyDangerousMemberName(ast.name),context,create,self.expression);case AST.MemberExpression:return left=this.recurse(ast.object,!1,!!create),ast.computed||(ensureSafeMemberName(ast.property.name,self.expression),right=ast.property.name),ast.computed&&(right=this.recurse(ast.property)),ast.computed?this.computedMember(left,right,context,create,self.expression):this.nonComputedMember(left,right,self.expensiveChecks,context,create,self.expression);case AST.CallExpression:return args=[],forEach(ast.arguments,function(expr){args.push(self.recurse(expr))}),ast.filter&&(right=this.$filter(ast.callee.name)),ast.filter||(right=this.recurse(ast.callee,!0)),ast.filter?function(scope,locals,assign,inputs){for(var values=[],i=0;i":function(left,right,context){return function(scope,locals,assign,inputs){var arg=left(scope,locals,assign,inputs)>right(scope,locals,assign,inputs);return context?{value:arg}:arg}},"binary<=":function(left,right,context){return function(scope,locals,assign,inputs){var arg=left(scope,locals,assign,inputs)<=right(scope,locals,assign,inputs);return context?{value:arg}:arg}},"binary>=":function(left,right,context){return function(scope,locals,assign,inputs){var arg=left(scope,locals,assign,inputs)>=right(scope,locals,assign,inputs);return context?{value:arg}:arg}},"binary&&":function(left,right,context){return function(scope,locals,assign,inputs){var arg=left(scope,locals,assign,inputs)&&right(scope,locals,assign,inputs);return context?{value:arg}:arg}},"binary||":function(left,right,context){return function(scope,locals,assign,inputs){var arg=left(scope,locals,assign,inputs)||right(scope,locals,assign,inputs);return context?{value:arg}:arg}},"ternary?:":function(test,alternate,consequent,context){return function(scope,locals,assign,inputs){var arg=test(scope,locals,assign,inputs)?alternate(scope,locals,assign,inputs):consequent(scope,locals,assign,inputs);return context?{value:arg}:arg}},value:function(value,context){return function(){return context?{context:undefined,name:undefined,value:value}:value}},identifier:function(name,expensiveChecks,context,create,expression){return function(scope,locals,assign,inputs){var base=locals&&name in locals?locals:scope;create&&1!==create&&base&&!base[name]&&(base[name]={});var value=base?base[name]:undefined;return expensiveChecks&&ensureSafeObject(value,expression),context?{context:base,name:name,value:value}:value}},computedMember:function(left,right,context,create,expression){return function(scope,locals,assign,inputs){var rhs,value,lhs=left(scope,locals,assign,inputs);return null!=lhs&&(rhs=right(scope,locals,assign,inputs),ensureSafeMemberName(rhs,expression),create&&1!==create&&lhs&&!lhs[rhs]&&(lhs[rhs]={}),value=lhs[rhs],ensureSafeObject(value,expression)),context?{context:lhs,name:rhs,value:value}:value}},nonComputedMember:function(left,right,expensiveChecks,context,create,expression){return function(scope,locals,assign,inputs){var lhs=left(scope,locals,assign,inputs);create&&1!==create&&lhs&&!lhs[right]&&(lhs[right]={});var value=null!=lhs?lhs[right]:undefined;return(expensiveChecks||isPossiblyDangerousMemberName(right))&&ensureSafeObject(value,expression), +context?{context:lhs,name:right,value:value}:value}},inputs:function(input,watchId){return function(scope,value,locals,inputs){return inputs?inputs[watchId]:input(scope,value,locals)}}};var Parser=function(lexer,$filter,options){this.lexer=lexer,this.$filter=$filter,this.options=options,this.ast=new AST(this.lexer),this.astCompiler=options.csp?new ASTInterpreter(this.ast,$filter):new ASTCompiler(this.ast,$filter)};Parser.prototype={constructor:Parser,parse:function(text){return this.astCompiler.compile(text,this.options.expensiveChecks)}};var objectValueOf=(createMap(),createMap(),Object.prototype.valueOf),$sceMinErr=minErr("$sce"),SCE_CONTEXTS={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},$compileMinErr=minErr("$compile"),urlParsingNode=document.createElement("a"),originUrl=urlResolve(window.location.href);$$CookieReader.$inject=["$document"],$FilterProvider.$inject=["$provide"],currencyFilter.$inject=["$locale"],numberFilter.$inject=["$locale"];var DECIMAL_SEP=".",DATE_FORMATS={yyyy:dateGetter("FullYear",4),yy:dateGetter("FullYear",2,0,!0),y:dateGetter("FullYear",1),MMMM:dateStrGetter("Month"),MMM:dateStrGetter("Month",!0),MM:dateGetter("Month",2,1),M:dateGetter("Month",1,1),dd:dateGetter("Date",2),d:dateGetter("Date",1),HH:dateGetter("Hours",2),H:dateGetter("Hours",1),hh:dateGetter("Hours",2,-12),h:dateGetter("Hours",1,-12),mm:dateGetter("Minutes",2),m:dateGetter("Minutes",1),ss:dateGetter("Seconds",2),s:dateGetter("Seconds",1),sss:dateGetter("Milliseconds",3),EEEE:dateStrGetter("Day"),EEE:dateStrGetter("Day",!0),a:ampmGetter,Z:timeZoneGetter,ww:weekGetter(2),w:weekGetter(1),G:eraGetter,GG:eraGetter,GGG:eraGetter,GGGG:longEraGetter},DATE_FORMATS_SPLIT=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,NUMBER_STRING=/^\-?\d+$/;dateFilter.$inject=["$locale"];var lowercaseFilter=valueFn(lowercase),uppercaseFilter=valueFn(uppercase);orderByFilter.$inject=["$parse"];var htmlAnchorDirective=valueFn({restrict:"E",compile:function(element,attr){return attr.href||attr.xlinkHref?void 0:function(scope,element){if("a"===element[0].nodeName.toLowerCase()){var href="[object SVGAnimatedString]"===toString.call(element.prop("href"))?"xlink:href":"href";element.on("click",function(event){element.attr(href)||event.preventDefault()})}}}}),ngAttributeAliasDirectives={};forEach(BOOLEAN_ATTR,function(propName,attrName){function defaultLinkFn(scope,element,attr){scope.$watch(attr[normalized],function(value){attr.$set(attrName,!!value)})}if("multiple"!=propName){var normalized=directiveNormalize("ng-"+attrName),linkFn=defaultLinkFn;"checked"===propName&&(linkFn=function(scope,element,attr){attr.ngModel!==attr[normalized]&&defaultLinkFn(scope,element,attr)}),ngAttributeAliasDirectives[normalized]=function(){return{restrict:"A",priority:100,link:linkFn}}}}),forEach(ALIASED_ATTR,function(htmlAttr,ngAttr){ngAttributeAliasDirectives[ngAttr]=function(){return{priority:100,link:function(scope,element,attr){if("ngPattern"===ngAttr&&"/"==attr.ngPattern.charAt(0)){var match=attr.ngPattern.match(REGEX_STRING_REGEXP);if(match)return void attr.$set("ngPattern",new RegExp(match[1],match[2]))}scope.$watch(attr[ngAttr],function(value){attr.$set(ngAttr,value)})}}}}),forEach(["src","srcset","href"],function(attrName){var normalized=directiveNormalize("ng-"+attrName);ngAttributeAliasDirectives[normalized]=function(){return{priority:99,link:function(scope,element,attr){var propName=attrName,name=attrName;"href"===attrName&&"[object SVGAnimatedString]"===toString.call(element.prop("href"))&&(name="xlinkHref",attr.$attr[name]="xlink:href",propName=null),attr.$observe(normalized,function(value){return value?(attr.$set(name,value),void(msie&&propName&&element.prop(propName,attr[name]))):void("href"===attrName&&attr.$set(name,null))})}}}});var nullFormCtrl={$addControl:noop,$$renameControl:nullFormRenameControl,$removeControl:noop,$setValidity:noop,$setDirty:noop,$setPristine:noop,$setSubmitted:noop},SUBMITTED_CLASS="ng-submitted";FormController.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var formDirectiveFactory=function(isNgForm){return["$timeout","$parse",function($timeout,$parse){function getSetter(expression){return""===expression?$parse('this[""]').assign:$parse(expression).assign||noop}var formDirective={name:"form",restrict:isNgForm?"EAC":"E",controller:FormController,compile:function(formElement,attr){formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);var nameAttr=attr.name?"name":isNgForm&&attr.ngForm?"ngForm":!1;return{pre:function(scope,formElement,attr,controller){if(!("action"in attr)){var handleFormSubmission=function(event){scope.$apply(function(){controller.$commitViewValue(),controller.$setSubmitted()}),event.preventDefault()};addEventListenerFn(formElement[0],"submit",handleFormSubmission),formElement.on("$destroy",function(){$timeout(function(){removeEventListenerFn(formElement[0],"submit",handleFormSubmission)},0,!1)})}var parentFormCtrl=controller.$$parentForm,setter=nameAttr?getSetter(controller.$name):noop;nameAttr&&(setter(scope,controller),attr.$observe(nameAttr,function(newValue){controller.$name!==newValue&&(setter(scope,undefined),parentFormCtrl.$$renameControl(controller,newValue),(setter=getSetter(controller.$name))(scope,controller))})),formElement.on("$destroy",function(){parentFormCtrl.$removeControl(controller),setter(scope,undefined),extend(controller,nullFormCtrl)})}}}};return formDirective}]},formDirective=formDirectiveFactory(),ngFormDirective=formDirectiveFactory(!0),ISO_DATE_REGEXP=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,URL_REGEXP=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,EMAIL_REGEXP=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,NUMBER_REGEXP=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,DATE_REGEXP=/^(\d{4})-(\d{2})-(\d{2})$/,DATETIMELOCAL_REGEXP=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,WEEK_REGEXP=/^(\d{4})-W(\d\d)$/,MONTH_REGEXP=/^(\d{4})-(\d\d)$/,TIME_REGEXP=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,inputType={text:textInputType,date:createDateInputType("date",DATE_REGEXP,createDateParser(DATE_REGEXP,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":createDateInputType("datetimelocal",DATETIMELOCAL_REGEXP,createDateParser(DATETIMELOCAL_REGEXP,["yyyy","MM","dd","HH","mm","ss","sss"]),"yyyy-MM-ddTHH:mm:ss.sss"),time:createDateInputType("time",TIME_REGEXP,createDateParser(TIME_REGEXP,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:createDateInputType("week",WEEK_REGEXP,weekParser,"yyyy-Www"),month:createDateInputType("month",MONTH_REGEXP,createDateParser(MONTH_REGEXP,["yyyy","MM"]),"yyyy-MM"),number:numberInputType,url:urlInputType,email:emailInputType,radio:radioInputType,checkbox:checkboxInputType,hidden:noop,button:noop,submit:noop,reset:noop,file:noop},inputDirective=["$browser","$sniffer","$filter","$parse",function($browser,$sniffer,$filter,$parse){return{restrict:"E",require:["?ngModel"],link:{pre:function(scope,element,attr,ctrls){ctrls[0]&&(inputType[lowercase(attr.type)]||inputType.text)(scope,element,attr,ctrls[0],$sniffer,$browser,$filter,$parse)}}}}],CONSTANT_VALUE_REGEXP=/^(true|false|\d+)$/,ngValueDirective=function(){return{restrict:"A",priority:100,compile:function(tpl,tplAttr){return CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)?function(scope,elm,attr){attr.$set("value",scope.$eval(attr.ngValue))}:function(scope,elm,attr){scope.$watch(attr.ngValue,function(value){attr.$set("value",value)})}}}},ngBindDirective=["$compile",function($compile){return{restrict:"AC",compile:function(templateElement){return $compile.$$addBindingClass(templateElement),function(scope,element,attr){$compile.$$addBindingInfo(element,attr.ngBind),element=element[0],scope.$watch(attr.ngBind,function(value){element.textContent=value===undefined?"":value})}}}}],ngBindTemplateDirective=["$interpolate","$compile",function($interpolate,$compile){return{compile:function(templateElement){return $compile.$$addBindingClass(templateElement),function(scope,element,attr){var interpolateFn=$interpolate(element.attr(attr.$attr.ngBindTemplate));$compile.$$addBindingInfo(element,interpolateFn.expressions),element=element[0],attr.$observe("ngBindTemplate",function(value){element.textContent=value===undefined?"":value})}}}}],ngBindHtmlDirective=["$sce","$parse","$compile",function($sce,$parse,$compile){return{restrict:"A",compile:function(tElement,tAttrs){var ngBindHtmlGetter=$parse(tAttrs.ngBindHtml),ngBindHtmlWatch=$parse(tAttrs.ngBindHtml,function(value){return(value||"").toString()});return $compile.$$addBindingClass(tElement),function(scope,element,attr){$compile.$$addBindingInfo(element,attr.ngBindHtml),scope.$watch(ngBindHtmlWatch,function(){element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope))||"")})}}}}],ngChangeDirective=valueFn({restrict:"A",require:"ngModel",link:function(scope,element,attr,ctrl){ctrl.$viewChangeListeners.push(function(){scope.$eval(attr.ngChange)})}}),ngClassDirective=classDirective("",!0),ngClassOddDirective=classDirective("Odd",0),ngClassEvenDirective=classDirective("Even",1),ngCloakDirective=ngDirective({compile:function(element,attr){attr.$set("ngCloak",undefined),element.removeClass("ng-cloak")}}),ngControllerDirective=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],ngEventDirectives={},forceAsyncEvents={blur:!0,focus:!0};forEach("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(eventName){var directiveName=directiveNormalize("ng-"+eventName);ngEventDirectives[directiveName]=["$parse","$rootScope",function($parse,$rootScope){return{restrict:"A",compile:function($element,attr){var fn=$parse(attr[directiveName],null,!0);return function(scope,element){element.on(eventName,function(event){var callback=function(){fn(scope,{$event:event})};forceAsyncEvents[eventName]&&$rootScope.$$phase?scope.$evalAsync(callback):scope.$apply(callback)})}}}}]});var ngIfDirective=["$animate",function($animate){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function($scope,$element,$attr,ctrl,$transclude){var block,childScope,previousElements;$scope.$watch($attr.ngIf,function(value){value?childScope||$transclude(function(clone,newScope){childScope=newScope,clone[clone.length++]=document.createComment(" end ngIf: "+$attr.ngIf+" "),block={clone:clone},$animate.enter(clone,$element.parent(),$element)}):(previousElements&&(previousElements.remove(),previousElements=null),childScope&&(childScope.$destroy(),childScope=null),block&&(previousElements=getBlockNodes(block.clone),$animate.leave(previousElements).then(function(){previousElements=null}),block=null))})}}}],ngIncludeDirective=["$templateRequest","$anchorScroll","$animate",function($templateRequest,$anchorScroll,$animate){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:angular.noop,compile:function(element,attr){var srcExp=attr.ngInclude||attr.src,onloadExp=attr.onload||"",autoScrollExp=attr.autoscroll;return function(scope,$element,$attr,ctrl,$transclude){var currentScope,previousElement,currentElement,changeCounter=0,cleanupLastIncludeContent=function(){previousElement&&(previousElement.remove(),previousElement=null),currentScope&&(currentScope.$destroy(),currentScope=null),currentElement&&($animate.leave(currentElement).then(function(){previousElement=null}),previousElement=currentElement,currentElement=null)};scope.$watch(srcExp,function(src){var afterAnimation=function(){!isDefined(autoScrollExp)||autoScrollExp&&!scope.$eval(autoScrollExp)||$anchorScroll()},thisChangeId=++changeCounter;src?($templateRequest(src,!0).then(function(response){if(thisChangeId===changeCounter){var newScope=scope.$new();ctrl.template=response;var clone=$transclude(newScope,function(clone){cleanupLastIncludeContent(),$animate.enter(clone,null,$element).then(afterAnimation)});currentScope=newScope,currentElement=clone,currentScope.$emit("$includeContentLoaded",src),scope.$eval(onloadExp)}},function(){thisChangeId===changeCounter&&(cleanupLastIncludeContent(),scope.$emit("$includeContentError",src))}),scope.$emit("$includeContentRequested",src)):(cleanupLastIncludeContent(),ctrl.template=null)})}}}}],ngIncludeFillContentDirective=["$compile",function($compile){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(scope,$element,$attr,ctrl){return/SVG/.test($element[0].toString())?($element.empty(),void $compile(jqLiteBuildFragment(ctrl.template,document).childNodes)(scope,function(clone){$element.append(clone)},{futureParentElement:$element})):($element.html(ctrl.template),void $compile($element.contents())(scope))}}}],ngInitDirective=ngDirective({priority:450,compile:function(){return{pre:function(scope,element,attrs){scope.$eval(attrs.ngInit)}}}}),ngListDirective=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(scope,element,attr,ctrl){var ngList=element.attr(attr.$attr.ngList)||", ",trimValues="false"!==attr.ngTrim,separator=trimValues?trim(ngList):ngList,parse=function(viewValue){if(!isUndefined(viewValue)){var list=[];return viewValue&&forEach(viewValue.split(separator),function(value){value&&list.push(trimValues?trim(value):value)}),list}};ctrl.$parsers.push(parse),ctrl.$formatters.push(function(value){return isArray(value)?value.join(ngList):undefined}),ctrl.$isEmpty=function(value){return!value||!value.length}}}},VALID_CLASS="ng-valid",INVALID_CLASS="ng-invalid",PRISTINE_CLASS="ng-pristine",DIRTY_CLASS="ng-dirty",UNTOUCHED_CLASS="ng-untouched",TOUCHED_CLASS="ng-touched",PENDING_CLASS="ng-pending",ngModelMinErr=minErr("ngModel"),NgModelController=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function($scope,$exceptionHandler,$attr,$element,$parse,$animate,$timeout,$rootScope,$q,$interpolate){this.$viewValue=Number.NaN,this.$modelValue=Number.NaN,this.$$rawModelValue=undefined,this.$validators={},this.$asyncValidators={},this.$parsers=[],this.$formatters=[],this.$viewChangeListeners=[],this.$untouched=!0,this.$touched=!1,this.$pristine=!0,this.$dirty=!1,this.$valid=!0,this.$invalid=!1,this.$error={},this.$$success={},this.$pending=undefined,this.$name=$interpolate($attr.name||"",!1)($scope);var parserValid,parsedNgModel=$parse($attr.ngModel),parsedNgModelAssign=parsedNgModel.assign,ngModelGet=parsedNgModel,ngModelSet=parsedNgModelAssign,pendingDebounce=null,ctrl=this;this.$$setOptions=function(options){if(ctrl.$options=options,options&&options.getterSetter){var invokeModelGetter=$parse($attr.ngModel+"()"),invokeModelSetter=$parse($attr.ngModel+"($$$p)");ngModelGet=function($scope){var modelValue=parsedNgModel($scope);return isFunction(modelValue)&&(modelValue=invokeModelGetter($scope)),modelValue},ngModelSet=function($scope,newValue){isFunction(parsedNgModel($scope))?invokeModelSetter($scope,{$$$p:ctrl.$modelValue}):parsedNgModelAssign($scope,ctrl.$modelValue)}}else if(!parsedNgModel.assign)throw ngModelMinErr("nonassign","Expression '{0}' is non-assignable. Element: {1}",$attr.ngModel,startingTag($element))},this.$render=noop,this.$isEmpty=function(value){return isUndefined(value)||""===value||null===value||value!==value};var parentForm=$element.inheritedData("$formController")||nullFormCtrl,currentValidationRunId=0;addSetValidityMethod({ctrl:this,$element:$element,set:function(object,property){object[property]=!0},unset:function(object,property){delete object[property]},parentForm:parentForm,$animate:$animate}),this.$setPristine=function(){ctrl.$dirty=!1,ctrl.$pristine=!0,$animate.removeClass($element,DIRTY_CLASS),$animate.addClass($element,PRISTINE_CLASS)},this.$setDirty=function(){ctrl.$dirty=!0,ctrl.$pristine=!1,$animate.removeClass($element,PRISTINE_CLASS),$animate.addClass($element,DIRTY_CLASS),parentForm.$setDirty()},this.$setUntouched=function(){ctrl.$touched=!1,ctrl.$untouched=!0,$animate.setClass($element,UNTOUCHED_CLASS,TOUCHED_CLASS)},this.$setTouched=function(){ctrl.$touched=!0,ctrl.$untouched=!1,$animate.setClass($element,TOUCHED_CLASS,UNTOUCHED_CLASS)},this.$rollbackViewValue=function(){$timeout.cancel(pendingDebounce),ctrl.$viewValue=ctrl.$$lastCommittedViewValue,ctrl.$render()},this.$validate=function(){if(!isNumber(ctrl.$modelValue)||!isNaN(ctrl.$modelValue)){var viewValue=ctrl.$$lastCommittedViewValue,modelValue=ctrl.$$rawModelValue,prevValid=ctrl.$valid,prevModelValue=ctrl.$modelValue,allowInvalid=ctrl.$options&&ctrl.$options.allowInvalid;ctrl.$$runValidators(modelValue,viewValue,function(allValid){allowInvalid||prevValid===allValid||(ctrl.$modelValue=allValid?modelValue:undefined,ctrl.$modelValue!==prevModelValue&&ctrl.$$writeModelToScope())})}},this.$$runValidators=function(modelValue,viewValue,doneCallback){function processParseErrors(){var errorKey=ctrl.$$parserName||"parse";return parserValid!==undefined?(parserValid||(forEach(ctrl.$validators,function(v,name){setValidity(name,null)}),forEach(ctrl.$asyncValidators,function(v,name){setValidity(name,null)})),setValidity(errorKey,parserValid),parserValid):(setValidity(errorKey,null),!0)}function processSyncValidators(){var syncValidatorsValid=!0;return forEach(ctrl.$validators,function(validator,name){var result=validator(modelValue,viewValue);syncValidatorsValid=syncValidatorsValid&&result,setValidity(name,result)}),syncValidatorsValid?!0:(forEach(ctrl.$asyncValidators,function(v,name){setValidity(name,null)}),!1)}function processAsyncValidators(){var validatorPromises=[],allValid=!0;forEach(ctrl.$asyncValidators,function(validator,name){var promise=validator(modelValue,viewValue);if(!isPromiseLike(promise))throw ngModelMinErr("$asyncValidators","Expected asynchronous validator to return a promise but got '{0}' instead.",promise);setValidity(name,undefined),validatorPromises.push(promise.then(function(){setValidity(name,!0)},function(error){allValid=!1,setValidity(name,!1)}))}),validatorPromises.length?$q.all(validatorPromises).then(function(){validationDone(allValid)},noop):validationDone(!0)}function setValidity(name,isValid){localValidationRunId===currentValidationRunId&&ctrl.$setValidity(name,isValid)}function validationDone(allValid){localValidationRunId===currentValidationRunId&&doneCallback(allValid)}currentValidationRunId++;var localValidationRunId=currentValidationRunId;return processParseErrors()&&processSyncValidators()?void processAsyncValidators():void validationDone(!1)},this.$commitViewValue=function(){var viewValue=ctrl.$viewValue;$timeout.cancel(pendingDebounce),(ctrl.$$lastCommittedViewValue!==viewValue||""===viewValue&&ctrl.$$hasNativeValidators)&&(ctrl.$$lastCommittedViewValue=viewValue,ctrl.$pristine&&this.$setDirty(),this.$$parseAndValidate())},this.$$parseAndValidate=function(){function writeToModelIfNeeded(){ctrl.$modelValue!==prevModelValue&&ctrl.$$writeModelToScope()}var viewValue=ctrl.$$lastCommittedViewValue,modelValue=viewValue;if(parserValid=isUndefined(modelValue)?undefined:!0)for(var i=0;iindex;index++){var key=optionValues===optionValuesKeys?index:optionValuesKeys[index],locals=(optionValues[key],getLocals(optionValues[key],key)),selectValue=getTrackByValueFn(optionValues[key],locals);if(watchedArray.push(selectValue),match[2]||match[1]){var label=displayFn(scope,locals);watchedArray.push(label)}if(match[4]){var disableWhen=disableWhenFn(scope,locals);watchedArray.push(disableWhen)}}return watchedArray}),getOptions:function(){for(var optionItems=[],selectValueMap={},optionValues=valuesFn(scope)||[],optionValuesKeys=getOptionValuesKeys(optionValues),optionValuesLength=optionValuesKeys.length,index=0;optionValuesLength>index;index++){var key=optionValues===optionValuesKeys?index:optionValuesKeys[index],value=optionValues[key],locals=getLocals(value,key),viewValue=viewValueFn(scope,locals),selectValue=getTrackByValueFn(viewValue,locals),label=displayFn(scope,locals),group=groupByFn(scope,locals),disabled=disableWhenFn(scope,locals),optionItem=new Option(selectValue,viewValue,label,group,disabled);optionItems.push(optionItem),selectValueMap[selectValue]=optionItem}return{items:optionItems,selectValueMap:selectValueMap,getOptionFromViewValue:function(value){return selectValueMap[getTrackByValue(value)]},getViewValueFromOption:function(option){return trackBy?angular.copy(option.viewValue):option.viewValue}}}}}var optionTemplate=document.createElement("option"),optGroupTemplate=document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","?ngModel"],link:function(scope,selectElement,attr,ctrls){function updateOptionElement(option,element){option.element=element,element.disabled=option.disabled,option.value!==element.value&&(element.value=option.selectValue),option.label!==element.label&&(element.label=option.label,element.textContent=option.label)}function addOrReuseElement(parent,current,type,templateElement){var element;return current&&lowercase(current.nodeName)===type?element=current:(element=templateElement.cloneNode(!1),current?parent.insertBefore(element,current):parent.appendChild(element)),element}function removeExcessElements(current){for(var next;current;)next=current.nextSibling,jqLiteRemove(current),current=next}function skipEmptyAndUnknownOptions(current){var emptyOption_=emptyOption&&emptyOption[0],unknownOption_=unknownOption&&unknownOption[0];if(emptyOption_||unknownOption_)for(;current&&(current===emptyOption_||current===unknownOption_);)current=current.nextSibling;return current}function updateOptions(){var previousValue=options&&selectCtrl.readValue();options=ngOptions.getOptions();var groupMap={},currentElement=selectElement[0].firstChild;if(providedEmptyOption&&selectElement.prepend(emptyOption),currentElement=skipEmptyAndUnknownOptions(currentElement),options.items.forEach(function(option){var group,groupElement,optionElement;option.group?(group=groupMap[option.group],group||(groupElement=addOrReuseElement(selectElement[0],currentElement,"optgroup",optGroupTemplate),currentElement=groupElement.nextSibling,groupElement.label=option.group,group=groupMap[option.group]={groupElement:groupElement,currentOptionElement:groupElement.firstChild}),optionElement=addOrReuseElement(group.groupElement,group.currentOptionElement,"option",optionTemplate),updateOptionElement(option,optionElement),group.currentOptionElement=optionElement.nextSibling):(optionElement=addOrReuseElement(selectElement[0],currentElement,"option",optionTemplate),updateOptionElement(option,optionElement),currentElement=optionElement.nextSibling)}),Object.keys(groupMap).forEach(function(key){removeExcessElements(groupMap[key].currentOptionElement)}),removeExcessElements(currentElement),ngModelCtrl.$render(),!ngModelCtrl.$isEmpty(previousValue)){var nextValue=selectCtrl.readValue();(ngOptions.trackBy?equals(previousValue,nextValue):previousValue===nextValue)||(ngModelCtrl.$setViewValue(nextValue),ngModelCtrl.$render())}}var ngModelCtrl=ctrls[1];if(ngModelCtrl){for(var emptyOption,selectCtrl=ctrls[0],multiple=attr.multiple,i=0,children=selectElement.children(),ii=children.length;ii>i;i++)if(""===children[i].value){emptyOption=children.eq(i);break}var providedEmptyOption=!!emptyOption,unknownOption=jqLite(optionTemplate.cloneNode(!1));unknownOption.val("?");var options,ngOptions=parseOptionsExpression(attr.ngOptions,selectElement,scope),renderEmptyOption=function(){providedEmptyOption||selectElement.prepend(emptyOption),selectElement.val(""),emptyOption.prop("selected",!0),emptyOption.attr("selected",!0)},removeEmptyOption=function(){providedEmptyOption||emptyOption.remove()},renderUnknownOption=function(){selectElement.prepend(unknownOption),selectElement.val("?"),unknownOption.prop("selected",!0),unknownOption.attr("selected",!0)},removeUnknownOption=function(){unknownOption.remove()};multiple?(ngModelCtrl.$isEmpty=function(value){return!value||0===value.length},selectCtrl.writeValue=function(value){options.items.forEach(function(option){option.element.selected=!1}),value&&value.forEach(function(item){var option=options.getOptionFromViewValue(item);option&&!option.disabled&&(option.element.selected=!0)})},selectCtrl.readValue=function(){var selectedValues=selectElement.val()||[],selections=[];return forEach(selectedValues,function(value){var option=options.selectValueMap[value];option&&!option.disabled&&selections.push(options.getViewValueFromOption(option))}),selections},ngOptions.trackBy&&scope.$watchCollection(function(){return isArray(ngModelCtrl.$viewValue)?ngModelCtrl.$viewValue.map(function(value){return ngOptions.getTrackByValue(value)}):void 0},function(){ngModelCtrl.$render()})):(selectCtrl.writeValue=function(value){var option=options.getOptionFromViewValue(value);option&&!option.disabled?selectElement[0].value!==option.selectValue&&(removeUnknownOption(),removeEmptyOption(),selectElement[0].value=option.selectValue,option.element.selected=!0,option.element.setAttribute("selected","selected")):null===value||providedEmptyOption?(removeUnknownOption(),renderEmptyOption()):(removeEmptyOption(),renderUnknownOption())},selectCtrl.readValue=function(){var selectedOption=options.selectValueMap[selectElement.val()];return selectedOption&&!selectedOption.disabled?(removeEmptyOption(),removeUnknownOption(),options.getViewValueFromOption(selectedOption)):null},ngOptions.trackBy&&scope.$watch(function(){return ngOptions.getTrackByValue(ngModelCtrl.$viewValue)},function(){ngModelCtrl.$render()})),providedEmptyOption?(emptyOption.remove(),$compile(emptyOption)(scope),emptyOption.removeClass("ng-scope")):emptyOption=jqLite(optionTemplate.cloneNode(!1)),updateOptions(),scope.$watchCollection(ngOptions.getWatchables,updateOptions)}}}}],ngPluralizeDirective=["$locale","$interpolate","$log",function($locale,$interpolate,$log){var BRACE=/{}/g,IS_WHEN=/^when(Minus)?(.+)$/;return{link:function(scope,element,attr){function updateElementText(newText){element.text(newText||"")}var lastCount,numberExp=attr.count,whenExp=attr.$attr.when&&element.attr(attr.$attr.when),offset=attr.offset||0,whens=scope.$eval(whenExp)||{},whensExpFns={},startSymbol=$interpolate.startSymbol(),endSymbol=$interpolate.endSymbol(),braceReplacement=startSymbol+numberExp+"-"+offset+endSymbol,watchRemover=angular.noop; +forEach(attr,function(expression,attributeName){var tmpMatch=IS_WHEN.exec(attributeName);if(tmpMatch){var whenKey=(tmpMatch[1]?"-":"")+lowercase(tmpMatch[2]);whens[whenKey]=element.attr(attr.$attr[attributeName])}}),forEach(whens,function(expression,key){whensExpFns[key]=$interpolate(expression.replace(BRACE,braceReplacement))}),scope.$watch(numberExp,function(newVal){var count=parseFloat(newVal),countIsNaN=isNaN(count);if(countIsNaN||count in whens||(count=$locale.pluralCat(count-offset)),count!==lastCount&&!(countIsNaN&&isNumber(lastCount)&&isNaN(lastCount))){watchRemover();var whenExpFn=whensExpFns[count];isUndefined(whenExpFn)?(null!=newVal&&$log.debug("ngPluralize: no rule defined for '"+count+"' in "+whenExp),watchRemover=noop,updateElementText()):watchRemover=scope.$watch(whenExpFn,updateElementText),lastCount=count}})}}}],ngRepeatDirective=["$parse","$animate",function($parse,$animate){var NG_REMOVED="$$NG_REMOVED",ngRepeatMinErr=minErr("ngRepeat"),updateScope=function(scope,index,valueIdentifier,value,keyIdentifier,key,arrayLength){scope[valueIdentifier]=value,keyIdentifier&&(scope[keyIdentifier]=key),scope.$index=index,scope.$first=0===index,scope.$last=index===arrayLength-1,scope.$middle=!(scope.$first||scope.$last),scope.$odd=!(scope.$even=0===(1&index))},getBlockStart=function(block){return block.clone[0]},getBlockEnd=function(block){return block.clone[block.clone.length-1]};return{restrict:"A",multiElement:!0,transclude:"element",priority:1e3,terminal:!0,$$tlb:!0,compile:function($element,$attr){var expression=$attr.ngRepeat,ngRepeatEndComment=document.createComment(" end ngRepeat: "+expression+" "),match=expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!match)throw ngRepeatMinErr("iexp","Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",expression);var lhs=match[1],rhs=match[2],aliasAs=match[3],trackByExp=match[4];if(match=lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/),!match)throw ngRepeatMinErr("iidexp","'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",lhs);var valueIdentifier=match[3]||match[1],keyIdentifier=match[2];if(aliasAs&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs)))throw ngRepeatMinErr("badident","alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",aliasAs);var trackByExpGetter,trackByIdExpFn,trackByIdArrayFn,trackByIdObjFn,hashFnLocals={$id:hashKey};return trackByExp?trackByExpGetter=$parse(trackByExp):(trackByIdArrayFn=function(key,value){return hashKey(value)},trackByIdObjFn=function(key){return key}),function($scope,$element,$attr,ctrl,$transclude){trackByExpGetter&&(trackByIdExpFn=function(key,value,index){return keyIdentifier&&(hashFnLocals[keyIdentifier]=key),hashFnLocals[valueIdentifier]=value,hashFnLocals.$index=index,trackByExpGetter($scope,hashFnLocals)});var lastBlockMap=createMap();$scope.$watchCollection(rhs,function(collection){var index,length,nextNode,collectionLength,key,value,trackById,trackByIdFn,collectionKeys,block,nextBlockOrder,elementsToRemove,previousNode=$element[0],nextBlockMap=createMap();if(aliasAs&&($scope[aliasAs]=collection),isArrayLike(collection))collectionKeys=collection,trackByIdFn=trackByIdExpFn||trackByIdArrayFn;else{trackByIdFn=trackByIdExpFn||trackByIdObjFn,collectionKeys=[];for(var itemKey in collection)collection.hasOwnProperty(itemKey)&&"$"!==itemKey.charAt(0)&&collectionKeys.push(itemKey)}for(collectionLength=collectionKeys.length,nextBlockOrder=new Array(collectionLength),index=0;collectionLength>index;index++)if(key=collection===collectionKeys?index:collectionKeys[index],value=collection[key],trackById=trackByIdFn(key,value,index),lastBlockMap[trackById])block=lastBlockMap[trackById],delete lastBlockMap[trackById],nextBlockMap[trackById]=block,nextBlockOrder[index]=block;else{if(nextBlockMap[trackById])throw forEach(nextBlockOrder,function(block){block&&block.scope&&(lastBlockMap[block.id]=block)}),ngRepeatMinErr("dupes","Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}",expression,trackById,value);nextBlockOrder[index]={id:trackById,scope:undefined,clone:undefined},nextBlockMap[trackById]=!0}for(var blockKey in lastBlockMap){if(block=lastBlockMap[blockKey],elementsToRemove=getBlockNodes(block.clone),$animate.leave(elementsToRemove),elementsToRemove[0].parentNode)for(index=0,length=elementsToRemove.length;length>index;index++)elementsToRemove[index][NG_REMOVED]=!0;block.scope.$destroy()}for(index=0;collectionLength>index;index++)if(key=collection===collectionKeys?index:collectionKeys[index],value=collection[key],block=nextBlockOrder[index],block.scope){nextNode=previousNode;do nextNode=nextNode.nextSibling;while(nextNode&&nextNode[NG_REMOVED]);getBlockStart(block)!=nextNode&&$animate.move(getBlockNodes(block.clone),null,jqLite(previousNode)),previousNode=getBlockEnd(block),updateScope(block.scope,index,valueIdentifier,value,keyIdentifier,key,collectionLength)}else $transclude(function(clone,scope){block.scope=scope;var endNode=ngRepeatEndComment.cloneNode(!1);clone[clone.length++]=endNode,$animate.enter(clone,null,jqLite(previousNode)),previousNode=endNode,block.clone=clone,nextBlockMap[block.id]=block,updateScope(block.scope,index,valueIdentifier,value,keyIdentifier,key,collectionLength)});lastBlockMap=nextBlockMap})}}}}],NG_HIDE_CLASS="ng-hide",NG_HIDE_IN_PROGRESS_CLASS="ng-hide-animate",ngShowDirective=["$animate",function($animate){return{restrict:"A",multiElement:!0,link:function(scope,element,attr){scope.$watch(attr.ngShow,function(value){$animate[value?"removeClass":"addClass"](element,NG_HIDE_CLASS,{tempClasses:NG_HIDE_IN_PROGRESS_CLASS})})}}}],ngHideDirective=["$animate",function($animate){return{restrict:"A",multiElement:!0,link:function(scope,element,attr){scope.$watch(attr.ngHide,function(value){$animate[value?"addClass":"removeClass"](element,NG_HIDE_CLASS,{tempClasses:NG_HIDE_IN_PROGRESS_CLASS})})}}}],ngStyleDirective=ngDirective(function(scope,element,attr){scope.$watch(attr.ngStyle,function(newStyles,oldStyles){oldStyles&&newStyles!==oldStyles&&forEach(oldStyles,function(val,style){element.css(style,"")}),newStyles&&element.css(newStyles)},!0)}),ngSwitchDirective=["$animate",function($animate){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(scope,element,attr,ngSwitchController){var watchExpr=attr.ngSwitch||attr.on,selectedTranscludes=[],selectedElements=[],previousLeaveAnimations=[],selectedScopes=[],spliceFactory=function(array,index){return function(){array.splice(index,1)}};scope.$watch(watchExpr,function(value){var i,ii;for(i=0,ii=previousLeaveAnimations.length;ii>i;++i)$animate.cancel(previousLeaveAnimations[i]);for(previousLeaveAnimations.length=0,i=0,ii=selectedScopes.length;ii>i;++i){var selected=getBlockNodes(selectedElements[i].clone);selectedScopes[i].$destroy();var promise=previousLeaveAnimations[i]=$animate.leave(selected);promise.then(spliceFactory(previousLeaveAnimations,i))}selectedElements.length=0,selectedScopes.length=0,(selectedTranscludes=ngSwitchController.cases["!"+value]||ngSwitchController.cases["?"])&&forEach(selectedTranscludes,function(selectedTransclude){selectedTransclude.transclude(function(caseElement,selectedScope){selectedScopes.push(selectedScope);var anchor=selectedTransclude.element;caseElement[caseElement.length++]=document.createComment(" end ngSwitchWhen: ");var block={clone:caseElement};selectedElements.push(block),$animate.enter(caseElement,anchor.parent(),anchor)})})})}}}],ngSwitchWhenDirective=ngDirective({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(scope,element,attrs,ctrl,$transclude){ctrl.cases["!"+attrs.ngSwitchWhen]=ctrl.cases["!"+attrs.ngSwitchWhen]||[],ctrl.cases["!"+attrs.ngSwitchWhen].push({transclude:$transclude,element:element})}}),ngSwitchDefaultDirective=ngDirective({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(scope,element,attr,ctrl,$transclude){ctrl.cases["?"]=ctrl.cases["?"]||[],ctrl.cases["?"].push({transclude:$transclude,element:element})}}),ngTranscludeDirective=ngDirective({restrict:"EAC",link:function($scope,$element,$attrs,controller,$transclude){if(!$transclude)throw minErr("ngTransclude")("orphan","Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: {0}",startingTag($element));$transclude(function(clone){$element.empty(),$element.append(clone)})}}),scriptDirective=["$templateCache",function($templateCache){return{restrict:"E",terminal:!0,compile:function(element,attr){if("text/ng-template"==attr.type){var templateUrl=attr.id,text=element[0].text;$templateCache.put(templateUrl,text)}}}}],noopNgModelController={$setViewValue:noop,$render:noop},SelectController=["$element","$scope","$attrs",function($element,$scope,$attrs){var self=this,optionsMap=new HashMap;self.ngModelCtrl=noopNgModelController,self.unknownOption=jqLite(document.createElement("option")),self.renderUnknownOption=function(val){var unknownVal="? "+hashKey(val)+" ?";self.unknownOption.val(unknownVal),$element.prepend(self.unknownOption),$element.val(unknownVal)},$scope.$on("$destroy",function(){self.renderUnknownOption=noop}),self.removeUnknownOption=function(){self.unknownOption.parent()&&self.unknownOption.remove()},self.readValue=function(){return self.removeUnknownOption(),$element.val()},self.writeValue=function(value){self.hasOption(value)?(self.removeUnknownOption(),$element.val(value),""===value&&self.emptyOption.prop("selected",!0)):null==value&&self.emptyOption?(self.removeUnknownOption(),$element.val("")):self.renderUnknownOption(value)},self.addOption=function(value,element){assertNotHasOwnProperty(value,'"option value"'),""===value&&(self.emptyOption=element);var count=optionsMap.get(value)||0;optionsMap.put(value,count+1)},self.removeOption=function(value){var count=optionsMap.get(value);count&&(1===count?(optionsMap.remove(value),""===value&&(self.emptyOption=undefined)):optionsMap.put(value,count-1))},self.hasOption=function(value){return!!optionsMap.get(value)}}],selectDirective=function(){return{restrict:"E",require:["select","?ngModel"],controller:SelectController,link:function(scope,element,attr,ctrls){var ngModelCtrl=ctrls[1];if(ngModelCtrl){var selectCtrl=ctrls[0];if(selectCtrl.ngModelCtrl=ngModelCtrl,ngModelCtrl.$render=function(){selectCtrl.writeValue(ngModelCtrl.$viewValue)},element.on("change",function(){scope.$apply(function(){ngModelCtrl.$setViewValue(selectCtrl.readValue())})}),attr.multiple){selectCtrl.readValue=function(){var array=[];return forEach(element.find("option"),function(option){option.selected&&array.push(option.value)}),array},selectCtrl.writeValue=function(value){var items=new HashMap(value);forEach(element.find("option"),function(option){option.selected=isDefined(items.get(option.value))})};var lastView,lastViewRef=NaN;scope.$watch(function(){lastViewRef!==ngModelCtrl.$viewValue||equals(lastView,ngModelCtrl.$viewValue)||(lastView=shallowCopy(ngModelCtrl.$viewValue),ngModelCtrl.$render()),lastViewRef=ngModelCtrl.$viewValue}),ngModelCtrl.$isEmpty=function(value){return!value||0===value.length}}}}}},optionDirective=["$interpolate",function($interpolate){function chromeHack(optionElement){optionElement[0].hasAttribute("selected")&&(optionElement[0].selected=!0)}return{restrict:"E",priority:100,compile:function(element,attr){if(isUndefined(attr.value)){var interpolateFn=$interpolate(element.text(),!0);interpolateFn||attr.$set("value",element.text())}return function(scope,element,attr){var selectCtrlName="$selectController",parent=element.parent(),selectCtrl=parent.data(selectCtrlName)||parent.parent().data(selectCtrlName);selectCtrl&&selectCtrl.ngModelCtrl&&(interpolateFn?scope.$watch(interpolateFn,function(newVal,oldVal){attr.$set("value",newVal),oldVal!==newVal&&selectCtrl.removeOption(oldVal),selectCtrl.addOption(newVal,element),selectCtrl.ngModelCtrl.$render(),chromeHack(element)}):(selectCtrl.addOption(attr.value,element),selectCtrl.ngModelCtrl.$render(),chromeHack(element)),element.on("$destroy",function(){selectCtrl.removeOption(attr.value),selectCtrl.ngModelCtrl.$render()}))}}}}],styleDirective=valueFn({restrict:"E",terminal:!1}),requiredDirective=function(){return{restrict:"A",require:"?ngModel",link:function(scope,elm,attr,ctrl){ctrl&&(attr.required=!0,ctrl.$validators.required=function(modelValue,viewValue){return!attr.required||!ctrl.$isEmpty(viewValue)},attr.$observe("required",function(){ctrl.$validate()}))}}},patternDirective=function(){return{restrict:"A",require:"?ngModel",link:function(scope,elm,attr,ctrl){if(ctrl){var regexp,patternExp=attr.ngPattern||attr.pattern;attr.$observe("pattern",function(regex){if(isString(regex)&®ex.length>0&&(regex=new RegExp("^"+regex+"$")),regex&&!regex.test)throw minErr("ngPattern")("noregexp","Expected {0} to be a RegExp but was {1}. Element: {2}",patternExp,regex,startingTag(elm));regexp=regex||undefined,ctrl.$validate()}),ctrl.$validators.pattern=function(value){return ctrl.$isEmpty(value)||isUndefined(regexp)||regexp.test(value)}}}}},maxlengthDirective=function(){return{restrict:"A",require:"?ngModel",link:function(scope,elm,attr,ctrl){if(ctrl){var maxlength=-1;attr.$observe("maxlength",function(value){var intVal=toInt(value);maxlength=isNaN(intVal)?-1:intVal,ctrl.$validate()}),ctrl.$validators.maxlength=function(modelValue,viewValue){return 0>maxlength||ctrl.$isEmpty(viewValue)||viewValue.length<=maxlength}}}}},minlengthDirective=function(){return{restrict:"A",require:"?ngModel",link:function(scope,elm,attr,ctrl){if(ctrl){var minlength=0;attr.$observe("minlength",function(value){minlength=toInt(value)||0,ctrl.$validate()}),ctrl.$validators.minlength=function(modelValue,viewValue){return ctrl.$isEmpty(viewValue)||viewValue.length>=minlength}}}}};return window.angular.bootstrap?void console.log("WARNING: Tried to load angular more than once."):(bindJQuery(),publishExternalAPI(angular),angular.module("ngLocale",[],["$provide",function($provide){function getDecimals(n){n+="";var i=n.indexOf(".");return-1==i?0:n.length-i-1}function getVF(n,opt_precision){var v=opt_precision;undefined===v&&(v=Math.min(getDecimals(n),3));var base=Math.pow(10,v),f=(n*base|0)%base;return{v:v,f:f}}var PLURAL_CATEGORY={ZERO:"zero",ONE:"one",TWO:"two",FEW:"few",MANY:"many",OTHER:"other"};$provide.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:["January","February","March","April","May","June","July","August","September","October","November","December"],SHORTDAY:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],SHORTMONTH:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-¤",negSuf:"",posPre:"¤",posSuf:""}]},id:"en-us",pluralCat:function(n,opt_precision){var i=0|n,vf=getVF(n,opt_precision);return 1==i&&0==vf.v?PLURAL_CATEGORY.ONE:PLURAL_CATEGORY.OTHER}})}]),void jqLite(document).ready(function(){angularInit(document,bootstrap)}))}(window,document),!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); + +},{}],18:[function(require,module,exports){ +require("./angular"),module.exports=angular; + +},{"./angular":17}],19:[function(require,module,exports){ + +},{}],20:[function(require,module,exports){ +function replacer(key,value){return util.isUndefined(value)?""+value:util.isNumber(value)&&!isFinite(value)?value.toString():util.isFunction(value)||util.isRegExp(value)?value.toString():value}function truncate(s,n){return util.isString(s)?s.length=0;i--)if(ka[i]!=kb[i])return!1;for(i=ka.length-1;i>=0;i--)if(key=ka[i],!_deepEqual(a[key],b[key]))return!1;return!0}function expectedException(actual,expected){return actual&&expected?"[object RegExp]"==Object.prototype.toString.call(expected)?expected.test(actual):actual instanceof expected?!0:expected.call({},actual)===!0?!0:!1:!1}function _throws(shouldThrow,block,expected,message){var actual;util.isString(expected)&&(message=expected,expected=null);try{block()}catch(e){actual=e}if(message=(expected&&expected.name?" ("+expected.name+").":".")+(message?" "+message:"."),shouldThrow&&!actual&&fail(actual,expected,"Missing expected exception"+message),!shouldThrow&&expectedException(actual,expected)&&fail(actual,expected,"Got unwanted exception"+message),shouldThrow&&actual&&expected&&!expectedException(actual,expected)||!shouldThrow&&actual)throw actual}var util=require("util/"),pSlice=Array.prototype.slice,hasOwn=Object.prototype.hasOwnProperty,assert=module.exports=ok;assert.AssertionError=function(options){this.name="AssertionError",this.actual=options.actual,this.expected=options.expected,this.operator=options.operator,options.message?(this.message=options.message,this.generatedMessage=!1):(this.message=getMessage(this),this.generatedMessage=!0);var stackStartFunction=options.stackStartFunction||fail;if(Error.captureStackTrace)Error.captureStackTrace(this,stackStartFunction);else{var err=new Error;if(err.stack){var out=err.stack,fn_name=stackStartFunction.name,idx=out.indexOf("\n"+fn_name);if(idx>=0){var next_line=out.indexOf("\n",idx+1);out=out.substring(next_line+1)}this.stack=out}}},util.inherits(assert.AssertionError,Error),assert.fail=fail,assert.ok=ok,assert.equal=function(actual,expected,message){actual!=expected&&fail(actual,expected,message,"==",assert.equal)},assert.notEqual=function(actual,expected,message){actual==expected&&fail(actual,expected,message,"!=",assert.notEqual)},assert.deepEqual=function(actual,expected,message){_deepEqual(actual,expected)||fail(actual,expected,message,"deepEqual",assert.deepEqual)},assert.notDeepEqual=function(actual,expected,message){_deepEqual(actual,expected)&&fail(actual,expected,message,"notDeepEqual",assert.notDeepEqual)},assert.strictEqual=function(actual,expected,message){actual!==expected&&fail(actual,expected,message,"===",assert.strictEqual)},assert.notStrictEqual=function(actual,expected,message){actual===expected&&fail(actual,expected,message,"!==",assert.notStrictEqual)},assert["throws"]=function(block,error,message){_throws.apply(this,[!0].concat(pSlice.call(arguments)))},assert.doesNotThrow=function(block,message){_throws.apply(this,[!1].concat(pSlice.call(arguments)))},assert.ifError=function(err){if(err)throw err};var objectKeys=Object.keys||function(obj){var keys=[];for(var key in obj)hasOwn.call(obj,key)&&keys.push(key);return keys}; + +},{"util/":25}],21:[function(require,module,exports){ +"function"==typeof Object.create?module.exports=function(ctor,superCtor){ctor.super_=superCtor,ctor.prototype=Object.create(superCtor.prototype,{constructor:{value:ctor,enumerable:!1,writable:!0,configurable:!0}})}:module.exports=function(ctor,superCtor){ctor.super_=superCtor;var TempCtor=function(){};TempCtor.prototype=superCtor.prototype,ctor.prototype=new TempCtor,ctor.prototype.constructor=ctor}; + +},{}],22:[function(require,module,exports){ +(function (process){ +function normalizeArray(parts,allowAboveRoot){for(var up=0,i=parts.length-1;i>=0;i--){var last=parts[i];"."===last?parts.splice(i,1):".."===last?(parts.splice(i,1),up++):up&&(parts.splice(i,1),up--)}if(allowAboveRoot)for(;up--;up)parts.unshift("..");return parts}function filter(xs,f){if(xs.filter)return xs.filter(f);for(var res=[],i=0;i=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if("string"!=typeof path)throw new TypeError("Arguments to path.resolve must be strings");path&&(resolvedPath=path+"/"+resolvedPath,resolvedAbsolute="/"===path.charAt(0))}return resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/"),(resolvedAbsolute?"/":"")+resolvedPath||"."},exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash="/"===substr(path,-1);return path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/"),path||isAbsolute||(path="."),path&&trailingSlash&&(path+="/"),(isAbsolute?"/":"")+path},exports.isAbsolute=function(path){return"/"===path.charAt(0)},exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if("string"!=typeof p)throw new TypeError("Arguments to path.join must be strings");return p}).join("/"))},exports.relative=function(from,to){function trim(arr){for(var start=0;start=0&&""===arr[end];end--);return start>end?[]:arr.slice(start,end-start+1)}from=exports.resolve(from).substr(1),to=exports.resolve(to).substr(1);for(var fromParts=trim(from.split("/")),toParts=trim(to.split("/")),length=Math.min(fromParts.length,toParts.length),samePartsLength=length,i=0;length>i;i++)if(fromParts[i]!==toParts[i]){samePartsLength=i;break}for(var outputParts=[],i=samePartsLength;istart&&(start=str.length+start),str.substr(start,len)}; + +}).call(this,require('_process')) +},{"_process":23}],23:[function(require,module,exports){ +function cleanUpNextTick(){draining=!1,currentQueue.length?queue=currentQueue.concat(queue):queueIndex=-1,queue.length&&drainQueue()}function drainQueue(){if(!draining){var timeout=setTimeout(cleanUpNextTick);draining=!0;for(var len=queue.length;len;){for(currentQueue=queue,queue=[];++queueIndex1)for(var i=1;i=3&&(ctx.depth=arguments[2]),arguments.length>=4&&(ctx.colors=arguments[3]),isBoolean(opts)?ctx.showHidden=opts:opts&&exports._extend(ctx,opts),isUndefined(ctx.showHidden)&&(ctx.showHidden=!1),isUndefined(ctx.depth)&&(ctx.depth=2),isUndefined(ctx.colors)&&(ctx.colors=!1),isUndefined(ctx.customInspect)&&(ctx.customInspect=!0),ctx.colors&&(ctx.stylize=stylizeWithColor),formatValue(ctx,obj,ctx.depth)}function stylizeWithColor(str,styleType){var style=inspect.styles[styleType];return style?"["+inspect.colors[style][0]+"m"+str+"["+inspect.colors[style][1]+"m":str}function stylizeNoColor(str,styleType){return str}function arrayToHash(array){var hash={};return array.forEach(function(val,idx){hash[val]=!0}),hash}function formatValue(ctx,value,recurseTimes){if(ctx.customInspect&&value&&isFunction(value.inspect)&&value.inspect!==exports.inspect&&(!value.constructor||value.constructor.prototype!==value)){var ret=value.inspect(recurseTimes,ctx);return isString(ret)||(ret=formatValue(ctx,ret,recurseTimes)),ret}var primitive=formatPrimitive(ctx,value);if(primitive)return primitive;var keys=Object.keys(value),visibleKeys=arrayToHash(keys);if(ctx.showHidden&&(keys=Object.getOwnPropertyNames(value)),isError(value)&&(keys.indexOf("message")>=0||keys.indexOf("description")>=0))return formatError(value);if(0===keys.length){if(isFunction(value)){var name=value.name?": "+value.name:"";return ctx.stylize("[Function"+name+"]","special")}if(isRegExp(value))return ctx.stylize(RegExp.prototype.toString.call(value),"regexp");if(isDate(value))return ctx.stylize(Date.prototype.toString.call(value),"date");if(isError(value))return formatError(value)}var base="",array=!1,braces=["{","}"];if(isArray(value)&&(array=!0,braces=["[","]"]),isFunction(value)){var n=value.name?": "+value.name:"";base=" [Function"+n+"]"}if(isRegExp(value)&&(base=" "+RegExp.prototype.toString.call(value)),isDate(value)&&(base=" "+Date.prototype.toUTCString.call(value)),isError(value)&&(base=" "+formatError(value)),0===keys.length&&(!array||0==value.length))return braces[0]+base+braces[1];if(0>recurseTimes)return isRegExp(value)?ctx.stylize(RegExp.prototype.toString.call(value),"regexp"):ctx.stylize("[Object]","special");ctx.seen.push(value);var output;return output=array?formatArray(ctx,value,recurseTimes,visibleKeys,keys):keys.map(function(key){return formatProperty(ctx,value,recurseTimes,visibleKeys,key,array)}),ctx.seen.pop(),reduceToSingleString(output,base,braces)}function formatPrimitive(ctx,value){if(isUndefined(value))return ctx.stylize("undefined","undefined");if(isString(value)){var simple="'"+JSON.stringify(value).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return ctx.stylize(simple,"string")}return isNumber(value)?ctx.stylize(""+value,"number"):isBoolean(value)?ctx.stylize(""+value,"boolean"):isNull(value)?ctx.stylize("null","null"):void 0}function formatError(value){return"["+Error.prototype.toString.call(value)+"]"}function formatArray(ctx,value,recurseTimes,visibleKeys,keys){for(var output=[],i=0,l=value.length;l>i;++i)hasOwnProperty(value,String(i))?output.push(formatProperty(ctx,value,recurseTimes,visibleKeys,String(i),!0)):output.push("");return keys.forEach(function(key){key.match(/^\d+$/)||output.push(formatProperty(ctx,value,recurseTimes,visibleKeys,key,!0))}),output}function formatProperty(ctx,value,recurseTimes,visibleKeys,key,array){var name,str,desc;if(desc=Object.getOwnPropertyDescriptor(value,key)||{value:value[key]},desc.get?str=desc.set?ctx.stylize("[Getter/Setter]","special"):ctx.stylize("[Getter]","special"):desc.set&&(str=ctx.stylize("[Setter]","special")),hasOwnProperty(visibleKeys,key)||(name="["+key+"]"),str||(ctx.seen.indexOf(desc.value)<0?(str=isNull(recurseTimes)?formatValue(ctx,desc.value,null):formatValue(ctx,desc.value,recurseTimes-1),str.indexOf("\n")>-1&&(str=array?str.split("\n").map(function(line){return" "+line}).join("\n").substr(2):"\n"+str.split("\n").map(function(line){return" "+line}).join("\n"))):str=ctx.stylize("[Circular]","special")),isUndefined(name)){if(array&&key.match(/^\d+$/))return str;name=JSON.stringify(""+key),name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(name=name.substr(1,name.length-2),name=ctx.stylize(name,"name")):(name=name.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),name=ctx.stylize(name,"string"))}return name+": "+str}function reduceToSingleString(output,base,braces){var numLinesEst=0,length=output.reduce(function(prev,cur){return numLinesEst++,cur.indexOf("\n")>=0&&numLinesEst++,prev+cur.replace(/\u001b\[\d\d?m/g,"").length+1},0);return length>60?braces[0]+(""===base?"":base+"\n ")+" "+output.join(",\n ")+" "+braces[1]:braces[0]+base+" "+output.join(", ")+" "+braces[1]}function isArray(ar){return Array.isArray(ar)}function isBoolean(arg){return"boolean"==typeof arg}function isNull(arg){return null===arg}function isNullOrUndefined(arg){return null==arg}function isNumber(arg){return"number"==typeof arg}function isString(arg){return"string"==typeof arg}function isSymbol(arg){return"symbol"==typeof arg}function isUndefined(arg){return void 0===arg}function isRegExp(re){return isObject(re)&&"[object RegExp]"===objectToString(re)}function isObject(arg){return"object"==typeof arg&&null!==arg}function isDate(d){return isObject(d)&&"[object Date]"===objectToString(d)}function isError(e){return isObject(e)&&("[object Error]"===objectToString(e)||e instanceof Error)}function isFunction(arg){return"function"==typeof arg}function isPrimitive(arg){return null===arg||"boolean"==typeof arg||"number"==typeof arg||"string"==typeof arg||"symbol"==typeof arg||"undefined"==typeof arg}function objectToString(o){return Object.prototype.toString.call(o)}function pad(n){return 10>n?"0"+n.toString(10):n.toString(10)}function timestamp(){var d=new Date,time=[pad(d.getHours()),pad(d.getMinutes()),pad(d.getSeconds())].join(":");return[d.getDate(),months[d.getMonth()],time].join(" ")}function hasOwnProperty(obj,prop){return Object.prototype.hasOwnProperty.call(obj,prop)}var formatRegExp=/%[sdj%]/g;exports.format=function(f){if(!isString(f)){for(var objects=[],i=0;i=len)return x;switch(x){case"%s":return String(args[i++]);case"%d":return Number(args[i++]);case"%j":try{return JSON.stringify(args[i++])}catch(_){return"[Circular]"}default:return x}}),x=args[i];len>i;x=args[++i])str+=isNull(x)||!isObject(x)?" "+x:" "+inspect(x);return str},exports.deprecate=function(fn,msg){function deprecated(){if(!warned){if(process.throwDeprecation)throw new Error(msg);process.traceDeprecation?console.trace(msg):console.error(msg),warned=!0}return fn.apply(this,arguments)}if(isUndefined(global.process))return function(){return exports.deprecate(fn,msg).apply(this,arguments)};if(process.noDeprecation===!0)return fn;var warned=!1;return deprecated};var debugs={},debugEnviron;exports.debuglog=function(set){if(isUndefined(debugEnviron)&&(debugEnviron=process.env.NODE_DEBUG||""),set=set.toUpperCase(),!debugs[set])if(new RegExp("\\b"+set+"\\b","i").test(debugEnviron)){var pid=process.pid;debugs[set]=function(){var msg=exports.format.apply(exports,arguments);console.error("%s %d: %s",set,pid,msg)}}else debugs[set]=function(){};return debugs[set]},exports.inspect=inspect,inspect.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},inspect.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"},exports.isArray=isArray,exports.isBoolean=isBoolean,exports.isNull=isNull,exports.isNullOrUndefined=isNullOrUndefined,exports.isNumber=isNumber,exports.isString=isString,exports.isSymbol=isSymbol,exports.isUndefined=isUndefined,exports.isRegExp=isRegExp,exports.isObject=isObject,exports.isDate=isDate,exports.isError=isError,exports.isFunction=isFunction,exports.isPrimitive=isPrimitive,exports.isBuffer=require("./support/isBuffer");var months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];exports.log=function(){console.log("%s - %s",timestamp(),exports.format.apply(exports,arguments))},exports.inherits=require("inherits"),exports._extend=function(origin,add){if(!add||!isObject(add))return origin;for(var keys=Object.keys(add),i=keys.length;i--;)origin[keys[i]]=add[keys[i]];return origin}; + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":24,"_process":23,"inherits":21}],26:[function(require,module,exports){ +!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):global.moment=factory()}(this,function(){"use strict";function utils_hooks__hooks(){return hookCallback.apply(null,arguments)}function setHookCallback(callback){hookCallback=callback}function isArray(input){return"[object Array]"===Object.prototype.toString.call(input)}function isDate(input){return input instanceof Date||"[object Date]"===Object.prototype.toString.call(input)}function map(arr,fn){var i,res=[];for(i=0;i0)for(i in momentProperties)prop=momentProperties[i],val=from[prop],"undefined"!=typeof val&&(to[prop]=val);return to}function Moment(config){copyConfig(this,config),this._d=new Date(null!=config._d?config._d.getTime():NaN),updateInProgress===!1&&(updateInProgress=!0,utils_hooks__hooks.updateOffset(this),updateInProgress=!1)}function isMoment(obj){return obj instanceof Moment||null!=obj&&null!=obj._isAMomentObject}function absFloor(number){return 0>number?Math.ceil(number):Math.floor(number)}function toInt(argumentForCoercion){var coercedNumber=+argumentForCoercion,value=0;return 0!==coercedNumber&&isFinite(coercedNumber)&&(value=absFloor(coercedNumber)),value}function compareArrays(array1,array2,dontConvert){var i,len=Math.min(array1.length,array2.length),lengthDiff=Math.abs(array1.length-array2.length),diffs=0;for(i=0;len>i;i++)(dontConvert&&array1[i]!==array2[i]||!dontConvert&&toInt(array1[i])!==toInt(array2[i]))&&diffs++;return diffs+lengthDiff}function Locale(){}function normalizeLocale(key){return key?key.toLowerCase().replace("_","-"):key}function chooseLocale(names){for(var j,next,locale,split,i=0;i0;){if(locale=loadLocale(split.slice(0,j).join("-")))return locale;if(next&&next.length>=j&&compareArrays(split,next,!0)>=j-1)break;j--}i++}return null}function loadLocale(name){var oldLocale=null;if(!locales[name]&&"undefined"!=typeof module&&module&&module.exports)try{oldLocale=globalLocale._abbr,require("./locale/"+name),locale_locales__getSetGlobalLocale(oldLocale)}catch(e){}return locales[name]}function locale_locales__getSetGlobalLocale(key,values){var data;return key&&(data="undefined"==typeof values?locale_locales__getLocale(key):defineLocale(key,values),data&&(globalLocale=data)),globalLocale._abbr}function defineLocale(name,values){return null!==values?(values.abbr=name,locales[name]=locales[name]||new Locale,locales[name].set(values),locale_locales__getSetGlobalLocale(name),locales[name]):(delete locales[name],null)}function locale_locales__getLocale(key){var locale;if(key&&key._locale&&key._locale._abbr&&(key=key._locale._abbr),!key)return globalLocale;if(!isArray(key)){if(locale=loadLocale(key))return locale;key=[key]}return chooseLocale(key)}function addUnitAlias(unit,shorthand){var lowerCase=unit.toLowerCase();aliases[lowerCase]=aliases[lowerCase+"s"]=aliases[shorthand]=unit}function normalizeUnits(units){return"string"==typeof units?aliases[units]||aliases[units.toLowerCase()]:void 0}function normalizeObjectUnits(inputObject){var normalizedProp,prop,normalizedInput={};for(prop in inputObject)hasOwnProp(inputObject,prop)&&(normalizedProp=normalizeUnits(prop),normalizedProp&&(normalizedInput[normalizedProp]=inputObject[prop]));return normalizedInput}function makeGetSet(unit,keepTime){return function(value){return null!=value?(get_set__set(this,unit,value),utils_hooks__hooks.updateOffset(this,keepTime),this):get_set__get(this,unit)}}function get_set__get(mom,unit){return mom._d["get"+(mom._isUTC?"UTC":"")+unit]()}function get_set__set(mom,unit,value){return mom._d["set"+(mom._isUTC?"UTC":"")+unit](value)}function getSet(units,value){var unit;if("object"==typeof units)for(unit in units)this.set(unit,units[unit]);else if(units=normalizeUnits(units),"function"==typeof this[units])return this[units](value);return this}function zeroFill(number,targetLength,forceSign){var absNumber=""+Math.abs(number),zerosToFill=targetLength-absNumber.length,sign=number>=0;return(sign?forceSign?"+":"":"-")+Math.pow(10,Math.max(0,zerosToFill)).toString().substr(1)+absNumber}function addFormatToken(token,padded,ordinal,callback){var func=callback;"string"==typeof callback&&(func=function(){return this[callback]()}),token&&(formatTokenFunctions[token]=func),padded&&(formatTokenFunctions[padded[0]]=function(){return zeroFill(func.apply(this,arguments),padded[1],padded[2])}),ordinal&&(formatTokenFunctions[ordinal]=function(){return this.localeData().ordinal(func.apply(this,arguments),token)})}function removeFormattingTokens(input){return input.match(/\[[\s\S]/)?input.replace(/^\[|\]$/g,""):input.replace(/\\/g,"")}function makeFormatFunction(format){var i,length,array=format.match(formattingTokens);for(i=0,length=array.length;length>i;i++)formatTokenFunctions[array[i]]?array[i]=formatTokenFunctions[array[i]]:array[i]=removeFormattingTokens(array[i]);return function(mom){var output="";for(i=0;length>i;i++)output+=array[i]instanceof Function?array[i].call(mom,format):array[i];return output}}function formatMoment(m,format){return m.isValid()?(format=expandFormat(format,m.localeData()),formatFunctions[format]=formatFunctions[format]||makeFormatFunction(format),formatFunctions[format](m)):m.localeData().invalidDate()}function expandFormat(format,locale){function replaceLongDateFormatTokens(input){return locale.longDateFormat(input)||input}var i=5;for(localFormattingTokens.lastIndex=0;i>=0&&localFormattingTokens.test(format);)format=format.replace(localFormattingTokens,replaceLongDateFormatTokens),localFormattingTokens.lastIndex=0,i-=1;return format}function isFunction(sth){return"function"==typeof sth&&"[object Function]"===Object.prototype.toString.call(sth)}function addRegexToken(token,regex,strictRegex){regexes[token]=isFunction(regex)?regex:function(isStrict){return isStrict&&strictRegex?strictRegex:regex}}function getParseRegexForToken(token,config){return hasOwnProp(regexes,token)?regexes[token](config._strict,config._locale):new RegExp(unescapeFormat(token))}function unescapeFormat(s){return s.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(matched,p1,p2,p3,p4){return p1||p2||p3||p4}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function addParseToken(token,callback){var i,func=callback;for("string"==typeof token&&(token=[token]),"number"==typeof callback&&(func=function(input,array){array[callback]=toInt(input)}),i=0;ii;i++){if(mom=create_utc__createUTC([2e3,i]),strict&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(mom,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(mom,"").replace(".","")+"$","i")),strict||this._monthsParse[i]||(regex="^"+this.months(mom,"")+"|^"+this.monthsShort(mom,""),this._monthsParse[i]=new RegExp(regex.replace(".",""),"i")),strict&&"MMMM"===format&&this._longMonthsParse[i].test(monthName))return i;if(strict&&"MMM"===format&&this._shortMonthsParse[i].test(monthName))return i;if(!strict&&this._monthsParse[i].test(monthName))return i}}function setMonth(mom,value){var dayOfMonth;return"string"==typeof value&&(value=mom.localeData().monthsParse(value),"number"!=typeof value)?mom:(dayOfMonth=Math.min(mom.date(),daysInMonth(mom.year(),value)),mom._d["set"+(mom._isUTC?"UTC":"")+"Month"](value,dayOfMonth),mom)}function getSetMonth(value){return null!=value?(setMonth(this,value),utils_hooks__hooks.updateOffset(this,!0),this):get_set__get(this,"Month")}function getDaysInMonth(){return daysInMonth(this.year(),this.month())}function checkOverflow(m){var overflow,a=m._a;return a&&-2===getParsingFlags(m).overflow&&(overflow=a[MONTH]<0||a[MONTH]>11?MONTH:a[DATE]<1||a[DATE]>daysInMonth(a[YEAR],a[MONTH])?DATE:a[HOUR]<0||a[HOUR]>24||24===a[HOUR]&&(0!==a[MINUTE]||0!==a[SECOND]||0!==a[MILLISECOND])?HOUR:a[MINUTE]<0||a[MINUTE]>59?MINUTE:a[SECOND]<0||a[SECOND]>59?SECOND:a[MILLISECOND]<0||a[MILLISECOND]>999?MILLISECOND:-1,getParsingFlags(m)._overflowDayOfYear&&(YEAR>overflow||overflow>DATE)&&(overflow=DATE),getParsingFlags(m).overflow=overflow),m}function warn(msg){utils_hooks__hooks.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+msg)}function deprecate(msg,fn){var firstTime=!0;return extend(function(){return firstTime&&(warn(msg+"\n"+(new Error).stack),firstTime=!1),fn.apply(this,arguments)},fn)}function deprecateSimple(name,msg){deprecations[name]||(warn(msg),deprecations[name]=!0)}function configFromISO(config){var i,l,string=config._i,match=from_string__isoRegex.exec(string);if(match){for(getParsingFlags(config).iso=!0,i=0,l=isoDates.length;l>i;i++)if(isoDates[i][1].exec(string)){config._f=isoDates[i][0];break}for(i=0,l=isoTimes.length;l>i;i++)if(isoTimes[i][1].exec(string)){config._f+=(match[6]||" ")+isoTimes[i][0];break}string.match(matchOffset)&&(config._f+="Z"),configFromStringAndFormat(config)}else config._isValid=!1}function configFromString(config){var matched=aspNetJsonRegex.exec(config._i);return null!==matched?void(config._d=new Date(+matched[1])):(configFromISO(config),void(config._isValid===!1&&(delete config._isValid,utils_hooks__hooks.createFromInputFallback(config))))}function createDate(y,m,d,h,M,s,ms){var date=new Date(y,m,d,h,M,s,ms);return 1970>y&&date.setFullYear(y),date}function createUTCDate(y){var date=new Date(Date.UTC.apply(null,arguments));return 1970>y&&date.setUTCFullYear(y),date}function daysInYear(year){return isLeapYear(year)?366:365}function isLeapYear(year){return year%4===0&&year%100!==0||year%400===0}function getIsLeapYear(){return isLeapYear(this.year())}function weekOfYear(mom,firstDayOfWeek,firstDayOfWeekOfYear){var adjustedMoment,end=firstDayOfWeekOfYear-firstDayOfWeek,daysToDayOfWeek=firstDayOfWeekOfYear-mom.day();return daysToDayOfWeek>end&&(daysToDayOfWeek-=7),end-7>daysToDayOfWeek&&(daysToDayOfWeek+=7),adjustedMoment=local__createLocal(mom).add(daysToDayOfWeek,"d"),{week:Math.ceil(adjustedMoment.dayOfYear()/7),year:adjustedMoment.year()}}function localeWeek(mom){return weekOfYear(mom,this._week.dow,this._week.doy).week}function localeFirstDayOfWeek(){return this._week.dow}function localeFirstDayOfYear(){return this._week.doy}function getSetWeek(input){var week=this.localeData().week(this);return null==input?week:this.add(7*(input-week),"d")}function getSetISOWeek(input){var week=weekOfYear(this,1,4).week;return null==input?week:this.add(7*(input-week),"d")}function dayOfYearFromWeeks(year,week,weekday,firstDayOfWeekOfYear,firstDayOfWeek){var dayOfYear,week1Jan=6+firstDayOfWeek-firstDayOfWeekOfYear,janX=createUTCDate(year,0,1+week1Jan),d=janX.getUTCDay();return firstDayOfWeek>d&&(d+=7),weekday=null!=weekday?1*weekday:firstDayOfWeek,dayOfYear=1+week1Jan+7*(week-1)-d+weekday,{year:dayOfYear>0?year:year-1,dayOfYear:dayOfYear>0?dayOfYear:daysInYear(year-1)+dayOfYear}}function getSetDayOfYear(input){var dayOfYear=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==input?dayOfYear:this.add(input-dayOfYear,"d")}function defaults(a,b,c){return null!=a?a:null!=b?b:c}function currentDateArray(config){var now=new Date;return config._useUTC?[now.getUTCFullYear(),now.getUTCMonth(),now.getUTCDate()]:[now.getFullYear(),now.getMonth(),now.getDate()]}function configFromArray(config){var i,date,currentDate,yearToUse,input=[];if(!config._d){for(currentDate=currentDateArray(config),config._w&&null==config._a[DATE]&&null==config._a[MONTH]&&dayOfYearFromWeekInfo(config),config._dayOfYear&&(yearToUse=defaults(config._a[YEAR],currentDate[YEAR]),config._dayOfYear>daysInYear(yearToUse)&&(getParsingFlags(config)._overflowDayOfYear=!0),date=createUTCDate(yearToUse,0,config._dayOfYear),config._a[MONTH]=date.getUTCMonth(),config._a[DATE]=date.getUTCDate()),i=0;3>i&&null==config._a[i];++i)config._a[i]=input[i]=currentDate[i];for(;7>i;i++)config._a[i]=input[i]=null==config._a[i]?2===i?1:0:config._a[i];24===config._a[HOUR]&&0===config._a[MINUTE]&&0===config._a[SECOND]&&0===config._a[MILLISECOND]&&(config._nextDay=!0,config._a[HOUR]=0),config._d=(config._useUTC?createUTCDate:createDate).apply(null,input),null!=config._tzm&&config._d.setUTCMinutes(config._d.getUTCMinutes()-config._tzm),config._nextDay&&(config._a[HOUR]=24)}}function dayOfYearFromWeekInfo(config){var w,weekYear,week,weekday,dow,doy,temp;w=config._w,null!=w.GG||null!=w.W||null!=w.E?(dow=1,doy=4,weekYear=defaults(w.GG,config._a[YEAR],weekOfYear(local__createLocal(),1,4).year),week=defaults(w.W,1),weekday=defaults(w.E,1)):(dow=config._locale._week.dow,doy=config._locale._week.doy,weekYear=defaults(w.gg,config._a[YEAR],weekOfYear(local__createLocal(),dow,doy).year),week=defaults(w.w,1),null!=w.d?(weekday=w.d,dow>weekday&&++week):weekday=null!=w.e?w.e+dow:dow),temp=dayOfYearFromWeeks(weekYear,week,weekday,doy,dow),config._a[YEAR]=temp.year,config._dayOfYear=temp.dayOfYear}function configFromStringAndFormat(config){if(config._f===utils_hooks__hooks.ISO_8601)return void configFromISO(config);config._a=[],getParsingFlags(config).empty=!0;var i,parsedInput,tokens,token,skipped,string=""+config._i,stringLength=string.length,totalParsedInputLength=0;for(tokens=expandFormat(config._f,config._locale).match(formattingTokens)||[],i=0;i0&&getParsingFlags(config).unusedInput.push(skipped),string=string.slice(string.indexOf(parsedInput)+parsedInput.length),totalParsedInputLength+=parsedInput.length),formatTokenFunctions[token]?(parsedInput?getParsingFlags(config).empty=!1:getParsingFlags(config).unusedTokens.push(token),addTimeToArrayFromToken(token,parsedInput,config)):config._strict&&!parsedInput&&getParsingFlags(config).unusedTokens.push(token);getParsingFlags(config).charsLeftOver=stringLength-totalParsedInputLength,string.length>0&&getParsingFlags(config).unusedInput.push(string),getParsingFlags(config).bigHour===!0&&config._a[HOUR]<=12&&config._a[HOUR]>0&&(getParsingFlags(config).bigHour=void 0),config._a[HOUR]=meridiemFixWrap(config._locale,config._a[HOUR],config._meridiem),configFromArray(config),checkOverflow(config)}function meridiemFixWrap(locale,hour,meridiem){var isPm;return null==meridiem?hour:null!=locale.meridiemHour?locale.meridiemHour(hour,meridiem):null!=locale.isPM?(isPm=locale.isPM(meridiem),isPm&&12>hour&&(hour+=12),isPm||12!==hour||(hour=0),hour):hour}function configFromStringAndArray(config){var tempConfig,bestMoment,scoreToBeat,i,currentScore;if(0===config._f.length)return getParsingFlags(config).invalidFormat=!0,void(config._d=new Date(NaN));for(i=0;icurrentScore)&&(scoreToBeat=currentScore,bestMoment=tempConfig));extend(config,bestMoment||tempConfig)}function configFromObject(config){if(!config._d){var i=normalizeObjectUnits(config._i);config._a=[i.year,i.month,i.day||i.date,i.hour,i.minute,i.second,i.millisecond],configFromArray(config)}}function createFromConfig(config){var res=new Moment(checkOverflow(prepareConfig(config)));return res._nextDay&&(res.add(1,"d"),res._nextDay=void 0),res}function prepareConfig(config){var input=config._i,format=config._f;return config._locale=config._locale||locale_locales__getLocale(config._l),null===input||void 0===format&&""===input?valid__createInvalid({nullInput:!0}):("string"==typeof input&&(config._i=input=config._locale.preparse(input)),isMoment(input)?new Moment(checkOverflow(input)):(isArray(format)?configFromStringAndArray(config):format?configFromStringAndFormat(config):isDate(input)?config._d=input:configFromInput(config),config))}function configFromInput(config){var input=config._i;void 0===input?config._d=new Date:isDate(input)?config._d=new Date(+input):"string"==typeof input?configFromString(config):isArray(input)?(config._a=map(input.slice(0),function(obj){return parseInt(obj,10)}),configFromArray(config)):"object"==typeof input?configFromObject(config):"number"==typeof input?config._d=new Date(input):utils_hooks__hooks.createFromInputFallback(config)}function createLocalOrUTC(input,format,locale,strict,isUTC){var c={};return"boolean"==typeof locale&&(strict=locale,locale=void 0),c._isAMomentObject=!0,c._useUTC=c._isUTC=isUTC,c._l=locale,c._i=input,c._f=format,c._strict=strict,createFromConfig(c)}function local__createLocal(input,format,locale,strict){return createLocalOrUTC(input,format,locale,strict,!1)}function pickBy(fn,moments){var res,i;if(1===moments.length&&isArray(moments[0])&&(moments=moments[0]),!moments.length)return local__createLocal();for(res=moments[0],i=1;ioffset&&(offset=-offset,sign="-"),sign+zeroFill(~~(offset/60),2)+separator+zeroFill(~~offset%60,2)})}function offsetFromString(string){var matches=(string||"").match(matchOffset)||[],chunk=matches[matches.length-1]||[],parts=(chunk+"").match(chunkOffset)||["-",0,0],minutes=+(60*parts[1])+toInt(parts[2]);return"+"===parts[0]?minutes:-minutes}function cloneWithOffset(input,model){var res,diff;return model._isUTC?(res=model.clone(),diff=(isMoment(input)||isDate(input)?+input:+local__createLocal(input))-+res,res._d.setTime(+res._d+diff),utils_hooks__hooks.updateOffset(res,!1),res):local__createLocal(input).local()}function getDateOffset(m){return 15*-Math.round(m._d.getTimezoneOffset()/15)}function getSetOffset(input,keepLocalTime){var localAdjust,offset=this._offset||0;return null!=input?("string"==typeof input&&(input=offsetFromString(input)),Math.abs(input)<16&&(input=60*input),!this._isUTC&&keepLocalTime&&(localAdjust=getDateOffset(this)),this._offset=input,this._isUTC=!0,null!=localAdjust&&this.add(localAdjust,"m"),offset!==input&&(!keepLocalTime||this._changeInProgress?add_subtract__addSubtract(this,create__createDuration(input-offset,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,utils_hooks__hooks.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?offset:getDateOffset(this)}function getSetZone(input,keepLocalTime){return null!=input?("string"!=typeof input&&(input=-input),this.utcOffset(input,keepLocalTime),this):-this.utcOffset()}function setOffsetToUTC(keepLocalTime){return this.utcOffset(0,keepLocalTime)}function setOffsetToLocal(keepLocalTime){return this._isUTC&&(this.utcOffset(0,keepLocalTime),this._isUTC=!1,keepLocalTime&&this.subtract(getDateOffset(this),"m")),this}function setOffsetToParsedOffset(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(offsetFromString(this._i)),this}function hasAlignedHourOffset(input){return input=input?local__createLocal(input).utcOffset():0,(this.utcOffset()-input)%60===0}function isDaylightSavingTime(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function isDaylightSavingTimeShifted(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var c={};if(copyConfig(c,this),c=prepareConfig(c),c._a){var other=c._isUTC?create_utc__createUTC(c._a):local__createLocal(c._a);this._isDSTShifted=this.isValid()&&compareArrays(c._a,other.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function isLocal(){return!this._isUTC}function isUtcOffset(){return this._isUTC}function isUtc(){return this._isUTC&&0===this._offset}function create__createDuration(input,key){var sign,ret,diffRes,duration=input,match=null;return isDuration(input)?duration={ms:input._milliseconds,d:input._days,M:input._months}:"number"==typeof input?(duration={},key?duration[key]=input:duration.milliseconds=input):(match=aspNetRegex.exec(input))?(sign="-"===match[1]?-1:1,duration={y:0,d:toInt(match[DATE])*sign,h:toInt(match[HOUR])*sign,m:toInt(match[MINUTE])*sign,s:toInt(match[SECOND])*sign,ms:toInt(match[MILLISECOND])*sign}):(match=create__isoRegex.exec(input))?(sign="-"===match[1]?-1:1,duration={y:parseIso(match[2],sign),M:parseIso(match[3],sign),d:parseIso(match[4],sign),h:parseIso(match[5],sign),m:parseIso(match[6],sign),s:parseIso(match[7],sign),w:parseIso(match[8],sign)}):null==duration?duration={}:"object"==typeof duration&&("from"in duration||"to"in duration)&&(diffRes=momentsDifference(local__createLocal(duration.from),local__createLocal(duration.to)),duration={},duration.ms=diffRes.milliseconds,duration.M=diffRes.months),ret=new Duration(duration),isDuration(input)&&hasOwnProp(input,"_locale")&&(ret._locale=input._locale),ret}function parseIso(inp,sign){var res=inp&&parseFloat(inp.replace(",","."));return(isNaN(res)?0:res)*sign}function positiveMomentsDifference(base,other){var res={milliseconds:0,months:0};return res.months=other.month()-base.month()+12*(other.year()-base.year()),base.clone().add(res.months,"M").isAfter(other)&&--res.months,res.milliseconds=+other-+base.clone().add(res.months,"M"),res}function momentsDifference(base,other){var res;return other=cloneWithOffset(other,base),base.isBefore(other)?res=positiveMomentsDifference(base,other):(res=positiveMomentsDifference(other,base),res.milliseconds=-res.milliseconds,res.months=-res.months),res}function createAdder(direction,name){return function(val,period){var dur,tmp;return null===period||isNaN(+period)||(deprecateSimple(name,"moment()."+name+"(period, number) is deprecated. Please use moment()."+name+"(number, period)."),tmp=val,val=period,period=tmp),val="string"==typeof val?+val:val,dur=create__createDuration(val,period),add_subtract__addSubtract(this,dur,direction),this}}function add_subtract__addSubtract(mom,duration,isAdding,updateOffset){var milliseconds=duration._milliseconds,days=duration._days,months=duration._months;updateOffset=null==updateOffset?!0:updateOffset,milliseconds&&mom._d.setTime(+mom._d+milliseconds*isAdding),days&&get_set__set(mom,"Date",get_set__get(mom,"Date")+days*isAdding),months&&setMonth(mom,get_set__get(mom,"Month")+months*isAdding),updateOffset&&utils_hooks__hooks.updateOffset(mom,days||months)}function moment_calendar__calendar(time,formats){var now=time||local__createLocal(),sod=cloneWithOffset(now,this).startOf("day"),diff=this.diff(sod,"days",!0),format=-6>diff?"sameElse":-1>diff?"lastWeek":0>diff?"lastDay":1>diff?"sameDay":2>diff?"nextDay":7>diff?"nextWeek":"sameElse";return this.format(formats&&formats[format]||this.localeData().calendar(format,this,local__createLocal(now)))}function clone(){return new Moment(this)}function isAfter(input,units){var inputMs;return units=normalizeUnits("undefined"!=typeof units?units:"millisecond"),"millisecond"===units?(input=isMoment(input)?input:local__createLocal(input),+this>+input):(inputMs=isMoment(input)?+input:+local__createLocal(input),inputMs<+this.clone().startOf(units))}function isBefore(input,units){var inputMs;return units=normalizeUnits("undefined"!=typeof units?units:"millisecond"),"millisecond"===units?(input=isMoment(input)?input:local__createLocal(input),+input>+this):(inputMs=isMoment(input)?+input:+local__createLocal(input),+this.clone().endOf(units)b-anchor?(anchor2=a.clone().add(wholeMonthDiff-1,"months"),adjust=(b-anchor)/(anchor-anchor2)):(anchor2=a.clone().add(wholeMonthDiff+1,"months"),adjust=(b-anchor)/(anchor2-anchor)),-(wholeMonthDiff+adjust)}function toString(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function moment_format__toISOString(){var m=this.clone().utc();return 0i;i++)if(this._weekdaysParse[i]||(mom=local__createLocal([2e3,1]).day(i),regex="^"+this.weekdays(mom,"")+"|^"+this.weekdaysShort(mom,"")+"|^"+this.weekdaysMin(mom,""),this._weekdaysParse[i]=new RegExp(regex.replace(".",""),"i")),this._weekdaysParse[i].test(weekdayName))return i}function getSetDayOfWeek(input){var day=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=input?(input=parseWeekday(input,this.localeData()),this.add(input-day,"d")):day}function getSetLocaleDayOfWeek(input){var weekday=(this.day()+7-this.localeData()._week.dow)%7;return null==input?weekday:this.add(input-weekday,"d")}function getSetISODayOfWeek(input){return null==input?this.day()||7:this.day(this.day()%7?input:input-7)}function meridiem(token,lowercase){addFormatToken(token,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),lowercase)})}function matchMeridiem(isStrict,locale){return locale._meridiemParse}function localeIsPM(input){return"p"===(input+"").toLowerCase().charAt(0)}function localeMeridiem(hours,minutes,isLower){return hours>11?isLower?"pm":"PM":isLower?"am":"AM"}function parseMs(input,array){array[MILLISECOND]=toInt(1e3*("0."+input))}function getZoneAbbr(){return this._isUTC?"UTC":""}function getZoneName(){return this._isUTC?"Coordinated Universal Time":""}function moment__createUnix(input){return local__createLocal(1e3*input)}function moment__createInZone(){return local__createLocal.apply(null,arguments).parseZone()}function locale_calendar__calendar(key,mom,now){var output=this._calendar[key];return"function"==typeof output?output.call(mom,now):output}function longDateFormat(key){var format=this._longDateFormat[key],formatUpper=this._longDateFormat[key.toUpperCase()];return format||!formatUpper?format:(this._longDateFormat[key]=formatUpper.replace(/MMMM|MM|DD|dddd/g,function(val){return val.slice(1)}),this._longDateFormat[key])}function invalidDate(){return this._invalidDate}function ordinal(number){return this._ordinal.replace("%d",number)}function preParsePostFormat(string){return string}function relative__relativeTime(number,withoutSuffix,string,isFuture){var output=this._relativeTime[string];return"function"==typeof output?output(number,withoutSuffix,string,isFuture):output.replace(/%d/i,number)}function pastFuture(diff,output){var format=this._relativeTime[diff>0?"future":"past"];return"function"==typeof format?format(output):format.replace(/%s/i,output)}function locale_set__set(config){var prop,i;for(i in config)prop=config[i],"function"==typeof prop?this[i]=prop:this["_"+i]=prop;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function lists__get(format,index,field,setter){var locale=locale_locales__getLocale(),utc=create_utc__createUTC().set(setter,index);return locale[field](utc,format)}function list(format,index,field,count,setter){if("number"==typeof format&&(index=format,format=void 0),format=format||"",null!=index)return lists__get(format,index,field,setter);var i,out=[];for(i=0;count>i;i++)out[i]=lists__get(format,i,field,setter);return out}function lists__listMonths(format,index){return list(format,index,"months",12,"month")}function lists__listMonthsShort(format,index){return list(format,index,"monthsShort",12,"month")}function lists__listWeekdays(format,index){return list(format,index,"weekdays",7,"day")}function lists__listWeekdaysShort(format,index){return list(format,index,"weekdaysShort",7,"day")}function lists__listWeekdaysMin(format,index){return list(format,index,"weekdaysMin",7,"day")}function duration_abs__abs(){var data=this._data;return this._milliseconds=mathAbs(this._milliseconds),this._days=mathAbs(this._days),this._months=mathAbs(this._months),data.milliseconds=mathAbs(data.milliseconds),data.seconds=mathAbs(data.seconds),data.minutes=mathAbs(data.minutes),data.hours=mathAbs(data.hours),data.months=mathAbs(data.months),data.years=mathAbs(data.years),this}function duration_add_subtract__addSubtract(duration,input,value,direction){var other=create__createDuration(input,value);return duration._milliseconds+=direction*other._milliseconds,duration._days+=direction*other._days,duration._months+=direction*other._months,duration._bubble()}function duration_add_subtract__add(input,value){return duration_add_subtract__addSubtract(this,input,value,1)}function duration_add_subtract__subtract(input,value){return duration_add_subtract__addSubtract(this,input,value,-1)}function absCeil(number){return 0>number?Math.floor(number):Math.ceil(number)}function bubble(){var seconds,minutes,hours,years,monthsFromDays,milliseconds=this._milliseconds,days=this._days,months=this._months,data=this._data;return milliseconds>=0&&days>=0&&months>=0||0>=milliseconds&&0>=days&&0>=months||(milliseconds+=864e5*absCeil(monthsToDays(months)+days),days=0,months=0),data.milliseconds=milliseconds%1e3,seconds=absFloor(milliseconds/1e3),data.seconds=seconds%60,minutes=absFloor(seconds/60),data.minutes=minutes%60,hours=absFloor(minutes/60),data.hours=hours%24,days+=absFloor(hours/24),monthsFromDays=absFloor(daysToMonths(days)),months+=monthsFromDays,days-=absCeil(monthsToDays(monthsFromDays)),years=absFloor(months/12),months%=12,data.days=days,data.months=months,data.years=years,this}function daysToMonths(days){return 4800*days/146097}function monthsToDays(months){return 146097*months/4800}function as(units){var days,months,milliseconds=this._milliseconds;if(units=normalizeUnits(units),"month"===units||"year"===units)return days=this._days+milliseconds/864e5,months=this._months+daysToMonths(days),"month"===units?months:months/12;switch(days=this._days+Math.round(monthsToDays(this._months)),units){case"week":return days/7+milliseconds/6048e5;case"day":return days+milliseconds/864e5;case"hour":return 24*days+milliseconds/36e5;case"minute":return 1440*days+milliseconds/6e4;case"second":return 86400*days+milliseconds/1e3;case"millisecond":return Math.floor(864e5*days)+milliseconds;default:throw new Error("Unknown unit "+units)}}function duration_as__valueOf(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*toInt(this._months/12)}function makeAs(alias){return function(){return this.as(alias)}}function duration_get__get(units){return units=normalizeUnits(units),this[units+"s"]()}function makeGetter(name){return function(){return this._data[name]}}function weeks(){return absFloor(this.days()/7)}function substituteTimeAgo(string,number,withoutSuffix,isFuture,locale){return locale.relativeTime(number||1,!!withoutSuffix,string,isFuture)}function duration_humanize__relativeTime(posNegDuration,withoutSuffix,locale){var duration=create__createDuration(posNegDuration).abs(),seconds=round(duration.as("s")),minutes=round(duration.as("m")),hours=round(duration.as("h")),days=round(duration.as("d")),months=round(duration.as("M")),years=round(duration.as("y")),a=seconds0,a[4]=locale,substituteTimeAgo.apply(null,a)}function duration_humanize__getSetRelativeTimeThreshold(threshold,limit){return void 0===thresholds[threshold]?!1:void 0===limit?thresholds[threshold]:(thresholds[threshold]=limit,!0)}function humanize(withSuffix){var locale=this.localeData(),output=duration_humanize__relativeTime(this,!withSuffix,locale);return withSuffix&&(output=locale.pastFuture(+this,output)),locale.postformat(output)}function iso_string__toISOString(){var minutes,hours,years,seconds=iso_string__abs(this._milliseconds)/1e3,days=iso_string__abs(this._days),months=iso_string__abs(this._months);minutes=absFloor(seconds/60),hours=absFloor(minutes/60),seconds%=60,minutes%=60,years=absFloor(months/12),months%=12;var Y=years,M=months,D=days,h=hours,m=minutes,s=seconds,total=this.asSeconds();return total?(0>total?"-":"")+"P"+(Y?Y+"Y":"")+(M?M+"M":"")+(D?D+"D":"")+(h||m||s?"T":"")+(h?h+"H":"")+(m?m+"M":"")+(s?s+"S":""):"P0D"}var hookCallback,globalLocale,momentProperties=utils_hooks__hooks.momentProperties=[],updateInProgress=!1,locales={},aliases={},formattingTokens=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,localFormattingTokens=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,formatFunctions={},formatTokenFunctions={},match1=/\d/,match2=/\d\d/,match3=/\d{3}/,match4=/\d{4}/,match6=/[+-]?\d{6}/,match1to2=/\d\d?/,match1to3=/\d{1,3}/,match1to4=/\d{1,4}/,match1to6=/[+-]?\d{1,6}/,matchUnsigned=/\d+/,matchSigned=/[+-]?\d+/,matchOffset=/Z|[+-]\d\d:?\d\d/gi,matchTimestamp=/[+-]?\d+(\.\d{1,3})?/,matchWord=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,regexes={},tokens={},YEAR=0,MONTH=1,DATE=2,HOUR=3,MINUTE=4,SECOND=5,MILLISECOND=6;addFormatToken("M",["MM",2],"Mo",function(){return this.month()+1}),addFormatToken("MMM",0,0,function(format){return this.localeData().monthsShort(this,format)}),addFormatToken("MMMM",0,0,function(format){return this.localeData().months(this,format)}),addUnitAlias("month","M"),addRegexToken("M",match1to2),addRegexToken("MM",match1to2,match2),addRegexToken("MMM",matchWord),addRegexToken("MMMM",matchWord),addParseToken(["M","MM"],function(input,array){array[MONTH]=toInt(input)-1}),addParseToken(["MMM","MMMM"],function(input,array,config,token){var month=config._locale.monthsParse(input,token,config._strict);null!=month?array[MONTH]=month:getParsingFlags(config).invalidMonth=input});var defaultLocaleMonths="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),defaultLocaleMonthsShort="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),deprecations={};utils_hooks__hooks.suppressDeprecationWarnings=!1;var from_string__isoRegex=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,isoDates=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],isoTimes=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],aspNetJsonRegex=/^\/?Date\((\-?\d+)/i;utils_hooks__hooks.createFromInputFallback=deprecate("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(config){config._d=new Date(config._i+(config._useUTC?" UTC":""))}),addFormatToken(0,["YY",2],0,function(){return this.year()%100}),addFormatToken(0,["YYYY",4],0,"year"),addFormatToken(0,["YYYYY",5],0,"year"),addFormatToken(0,["YYYYYY",6,!0],0,"year"),addUnitAlias("year","y"),addRegexToken("Y",matchSigned),addRegexToken("YY",match1to2,match2),addRegexToken("YYYY",match1to4,match4),addRegexToken("YYYYY",match1to6,match6),addRegexToken("YYYYYY",match1to6,match6),addParseToken(["YYYYY","YYYYYY"],YEAR),addParseToken("YYYY",function(input,array){array[YEAR]=2===input.length?utils_hooks__hooks.parseTwoDigitYear(input):toInt(input)}),addParseToken("YY",function(input,array){array[YEAR]=utils_hooks__hooks.parseTwoDigitYear(input)}),utils_hooks__hooks.parseTwoDigitYear=function(input){return toInt(input)+(toInt(input)>68?1900:2e3)};var getSetYear=makeGetSet("FullYear",!1);addFormatToken("w",["ww",2],"wo","week"),addFormatToken("W",["WW",2],"Wo","isoWeek"),addUnitAlias("week","w"),addUnitAlias("isoWeek","W"),addRegexToken("w",match1to2),addRegexToken("ww",match1to2,match2),addRegexToken("W",match1to2),addRegexToken("WW",match1to2,match2),addWeekParseToken(["w","ww","W","WW"],function(input,week,config,token){week[token.substr(0,1)]=toInt(input)});var defaultLocaleWeek={dow:0,doy:6};addFormatToken("DDD",["DDDD",3],"DDDo","dayOfYear"),addUnitAlias("dayOfYear","DDD"),addRegexToken("DDD",match1to3),addRegexToken("DDDD",match3),addParseToken(["DDD","DDDD"],function(input,array,config){config._dayOfYear=toInt(input)}),utils_hooks__hooks.ISO_8601=function(){};var prototypeMin=deprecate("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var other=local__createLocal.apply(null,arguments);return this>other?this:other}),prototypeMax=deprecate("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var other=local__createLocal.apply(null,arguments);return other>this?this:other});offset("Z",":"),offset("ZZ",""),addRegexToken("Z",matchOffset),addRegexToken("ZZ",matchOffset),addParseToken(["Z","ZZ"],function(input,array,config){config._useUTC=!0,config._tzm=offsetFromString(input)});var chunkOffset=/([\+\-]|\d\d)/gi;utils_hooks__hooks.updateOffset=function(){};var aspNetRegex=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,create__isoRegex=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;create__createDuration.fn=Duration.prototype;var add_subtract__add=createAdder(1,"add"),add_subtract__subtract=createAdder(-1,"subtract");utils_hooks__hooks.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var lang=deprecate("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(key){return void 0===key?this.localeData():this.locale(key)});addFormatToken(0,["gg",2],0,function(){return this.weekYear()%100}),addFormatToken(0,["GG",2],0,function(){return this.isoWeekYear()%100}),addWeekYearFormatToken("gggg","weekYear"),addWeekYearFormatToken("ggggg","weekYear"),addWeekYearFormatToken("GGGG","isoWeekYear"),addWeekYearFormatToken("GGGGG","isoWeekYear"),addUnitAlias("weekYear","gg"),addUnitAlias("isoWeekYear","GG"),addRegexToken("G",matchSigned),addRegexToken("g",matchSigned),addRegexToken("GG",match1to2,match2),addRegexToken("gg",match1to2,match2),addRegexToken("GGGG",match1to4,match4),addRegexToken("gggg",match1to4,match4),addRegexToken("GGGGG",match1to6,match6),addRegexToken("ggggg",match1to6,match6),addWeekParseToken(["gggg","ggggg","GGGG","GGGGG"],function(input,week,config,token){week[token.substr(0,2)]=toInt(input)}),addWeekParseToken(["gg","GG"],function(input,week,config,token){week[token]=utils_hooks__hooks.parseTwoDigitYear(input)}),addFormatToken("Q",0,0,"quarter"),addUnitAlias("quarter","Q"),addRegexToken("Q",match1),addParseToken("Q",function(input,array){array[MONTH]=3*(toInt(input)-1)}),addFormatToken("D",["DD",2],"Do","date"),addUnitAlias("date","D"),addRegexToken("D",match1to2),addRegexToken("DD",match1to2,match2),addRegexToken("Do",function(isStrict,locale){return isStrict?locale._ordinalParse:locale._ordinalParseLenient}),addParseToken(["D","DD"],DATE),addParseToken("Do",function(input,array){array[DATE]=toInt(input.match(match1to2)[0],10)});var getSetDayOfMonth=makeGetSet("Date",!0);addFormatToken("d",0,"do","day"),addFormatToken("dd",0,0,function(format){return this.localeData().weekdaysMin(this,format)}),addFormatToken("ddd",0,0,function(format){return this.localeData().weekdaysShort(this,format)}),addFormatToken("dddd",0,0,function(format){return this.localeData().weekdays(this,format)}),addFormatToken("e",0,0,"weekday"),addFormatToken("E",0,0,"isoWeekday"),addUnitAlias("day","d"),addUnitAlias("weekday","e"),addUnitAlias("isoWeekday","E"),addRegexToken("d",match1to2),addRegexToken("e",match1to2),addRegexToken("E",match1to2),addRegexToken("dd",matchWord),addRegexToken("ddd",matchWord),addRegexToken("dddd",matchWord),addWeekParseToken(["dd","ddd","dddd"],function(input,week,config){var weekday=config._locale.weekdaysParse(input);null!=weekday?week.d=weekday:getParsingFlags(config).invalidWeekday=input}),addWeekParseToken(["d","e","E"],function(input,week,config,token){week[token]=toInt(input)});var defaultLocaleWeekdays="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),defaultLocaleWeekdaysShort="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),defaultLocaleWeekdaysMin="Su_Mo_Tu_We_Th_Fr_Sa".split("_");addFormatToken("H",["HH",2],0,"hour"),addFormatToken("h",["hh",2],0,function(){return this.hours()%12||12}),meridiem("a",!0),meridiem("A",!1),addUnitAlias("hour","h"),addRegexToken("a",matchMeridiem),addRegexToken("A",matchMeridiem),addRegexToken("H",match1to2),addRegexToken("h",match1to2),addRegexToken("HH",match1to2,match2),addRegexToken("hh",match1to2,match2),addParseToken(["H","HH"],HOUR),addParseToken(["a","A"],function(input,array,config){config._isPm=config._locale.isPM(input),config._meridiem=input}),addParseToken(["h","hh"],function(input,array,config){array[HOUR]=toInt(input),getParsingFlags(config).bigHour=!0});var defaultLocaleMeridiemParse=/[ap]\.?m?\.?/i,getSetHour=makeGetSet("Hours",!0);addFormatToken("m",["mm",2],0,"minute"),addUnitAlias("minute","m"),addRegexToken("m",match1to2),addRegexToken("mm",match1to2,match2),addParseToken(["m","mm"],MINUTE);var getSetMinute=makeGetSet("Minutes",!1);addFormatToken("s",["ss",2],0,"second"),addUnitAlias("second","s"),addRegexToken("s",match1to2),addRegexToken("ss",match1to2,match2),addParseToken(["s","ss"],SECOND);var getSetSecond=makeGetSet("Seconds",!1);addFormatToken("S",0,0,function(){return~~(this.millisecond()/100)}),addFormatToken(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),addFormatToken(0,["SSS",3],0,"millisecond"),addFormatToken(0,["SSSS",4],0,function(){return 10*this.millisecond()}),addFormatToken(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),addFormatToken(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),addFormatToken(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),addFormatToken(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),addFormatToken(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),addUnitAlias("millisecond","ms"),addRegexToken("S",match1to3,match1),addRegexToken("SS",match1to3,match2),addRegexToken("SSS",match1to3,match3);var token;for(token="SSSS";token.length<=9;token+="S")addRegexToken(token,matchUnsigned);for(token="S";token.length<=9;token+="S")addParseToken(token,parseMs);var getSetMillisecond=makeGetSet("Milliseconds",!1);addFormatToken("z",0,0,"zoneAbbr"),addFormatToken("zz",0,0,"zoneName");var momentPrototype__proto=Moment.prototype;momentPrototype__proto.add=add_subtract__add,momentPrototype__proto.calendar=moment_calendar__calendar,momentPrototype__proto.clone=clone,momentPrototype__proto.diff=diff,momentPrototype__proto.endOf=endOf,momentPrototype__proto.format=format,momentPrototype__proto.from=from,momentPrototype__proto.fromNow=fromNow,momentPrototype__proto.to=to,momentPrototype__proto.toNow=toNow,momentPrototype__proto.get=getSet,momentPrototype__proto.invalidAt=invalidAt,momentPrototype__proto.isAfter=isAfter,momentPrototype__proto.isBefore=isBefore,momentPrototype__proto.isBetween=isBetween,momentPrototype__proto.isSame=isSame,momentPrototype__proto.isValid=moment_valid__isValid,momentPrototype__proto.lang=lang,momentPrototype__proto.locale=locale,momentPrototype__proto.localeData=localeData,momentPrototype__proto.max=prototypeMax,momentPrototype__proto.min=prototypeMin,momentPrototype__proto.parsingFlags=parsingFlags,momentPrototype__proto.set=getSet,momentPrototype__proto.startOf=startOf,momentPrototype__proto.subtract=add_subtract__subtract,momentPrototype__proto.toArray=toArray,momentPrototype__proto.toObject=toObject,momentPrototype__proto.toDate=toDate,momentPrototype__proto.toISOString=moment_format__toISOString,momentPrototype__proto.toJSON=moment_format__toISOString,momentPrototype__proto.toString=toString,momentPrototype__proto.unix=unix,momentPrototype__proto.valueOf=to_type__valueOf,momentPrototype__proto.year=getSetYear,momentPrototype__proto.isLeapYear=getIsLeapYear,momentPrototype__proto.weekYear=getSetWeekYear,momentPrototype__proto.isoWeekYear=getSetISOWeekYear,momentPrototype__proto.quarter=momentPrototype__proto.quarters=getSetQuarter,momentPrototype__proto.month=getSetMonth,momentPrototype__proto.daysInMonth=getDaysInMonth,momentPrototype__proto.week=momentPrototype__proto.weeks=getSetWeek,momentPrototype__proto.isoWeek=momentPrototype__proto.isoWeeks=getSetISOWeek,momentPrototype__proto.weeksInYear=getWeeksInYear,momentPrototype__proto.isoWeeksInYear=getISOWeeksInYear,momentPrototype__proto.date=getSetDayOfMonth,momentPrototype__proto.day=momentPrototype__proto.days=getSetDayOfWeek,momentPrototype__proto.weekday=getSetLocaleDayOfWeek,momentPrototype__proto.isoWeekday=getSetISODayOfWeek,momentPrototype__proto.dayOfYear=getSetDayOfYear,momentPrototype__proto.hour=momentPrototype__proto.hours=getSetHour,momentPrototype__proto.minute=momentPrototype__proto.minutes=getSetMinute,momentPrototype__proto.second=momentPrototype__proto.seconds=getSetSecond,momentPrototype__proto.millisecond=momentPrototype__proto.milliseconds=getSetMillisecond,momentPrototype__proto.utcOffset=getSetOffset,momentPrototype__proto.utc=setOffsetToUTC,momentPrototype__proto.local=setOffsetToLocal,momentPrototype__proto.parseZone=setOffsetToParsedOffset,momentPrototype__proto.hasAlignedHourOffset=hasAlignedHourOffset,momentPrototype__proto.isDST=isDaylightSavingTime,momentPrototype__proto.isDSTShifted=isDaylightSavingTimeShifted,momentPrototype__proto.isLocal=isLocal,momentPrototype__proto.isUtcOffset=isUtcOffset,momentPrototype__proto.isUtc=isUtc,momentPrototype__proto.isUTC=isUtc,momentPrototype__proto.zoneAbbr=getZoneAbbr,momentPrototype__proto.zoneName=getZoneName,momentPrototype__proto.dates=deprecate("dates accessor is deprecated. Use date instead.",getSetDayOfMonth),momentPrototype__proto.months=deprecate("months accessor is deprecated. Use month instead",getSetMonth),momentPrototype__proto.years=deprecate("years accessor is deprecated. Use year instead",getSetYear),momentPrototype__proto.zone=deprecate("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",getSetZone);var momentPrototype=momentPrototype__proto,defaultCalendar={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},defaultLongDateFormat={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},defaultInvalidDate="Invalid date",defaultOrdinal="%d",defaultOrdinalParse=/\d{1,2}/,defaultRelativeTime={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},prototype__proto=Locale.prototype;prototype__proto._calendar=defaultCalendar,prototype__proto.calendar=locale_calendar__calendar,prototype__proto._longDateFormat=defaultLongDateFormat,prototype__proto.longDateFormat=longDateFormat,prototype__proto._invalidDate=defaultInvalidDate,prototype__proto.invalidDate=invalidDate,prototype__proto._ordinal=defaultOrdinal,prototype__proto.ordinal=ordinal,prototype__proto._ordinalParse=defaultOrdinalParse,prototype__proto.preparse=preParsePostFormat,prototype__proto.postformat=preParsePostFormat,prototype__proto._relativeTime=defaultRelativeTime,prototype__proto.relativeTime=relative__relativeTime,prototype__proto.pastFuture=pastFuture,prototype__proto.set=locale_set__set,prototype__proto.months=localeMonths,prototype__proto._months=defaultLocaleMonths,prototype__proto.monthsShort=localeMonthsShort,prototype__proto._monthsShort=defaultLocaleMonthsShort,prototype__proto.monthsParse=localeMonthsParse,prototype__proto.week=localeWeek,prototype__proto._week=defaultLocaleWeek,prototype__proto.firstDayOfYear=localeFirstDayOfYear,prototype__proto.firstDayOfWeek=localeFirstDayOfWeek,prototype__proto.weekdays=localeWeekdays,prototype__proto._weekdays=defaultLocaleWeekdays,prototype__proto.weekdaysMin=localeWeekdaysMin,prototype__proto._weekdaysMin=defaultLocaleWeekdaysMin,prototype__proto.weekdaysShort=localeWeekdaysShort,prototype__proto._weekdaysShort=defaultLocaleWeekdaysShort,prototype__proto.weekdaysParse=localeWeekdaysParse,prototype__proto.isPM=localeIsPM,prototype__proto._meridiemParse=defaultLocaleMeridiemParse,prototype__proto.meridiem=localeMeridiem,locale_locales__getSetGlobalLocale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(number){var b=number%10,output=1===toInt(number%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return number+output}}),utils_hooks__hooks.lang=deprecate("moment.lang is deprecated. Use moment.locale instead.",locale_locales__getSetGlobalLocale),utils_hooks__hooks.langData=deprecate("moment.langData is deprecated. Use moment.localeData instead.",locale_locales__getLocale);var mathAbs=Math.abs,asMilliseconds=makeAs("ms"),asSeconds=makeAs("s"),asMinutes=makeAs("m"),asHours=makeAs("h"),asDays=makeAs("d"),asWeeks=makeAs("w"),asMonths=makeAs("M"),asYears=makeAs("y"),milliseconds=makeGetter("milliseconds"),seconds=makeGetter("seconds"),minutes=makeGetter("minutes"),hours=makeGetter("hours"),days=makeGetter("days"),months=makeGetter("months"),years=makeGetter("years"),round=Math.round,thresholds={s:45,m:45,h:22,d:26,M:11},iso_string__abs=Math.abs,duration_prototype__proto=Duration.prototype;duration_prototype__proto.abs=duration_abs__abs,duration_prototype__proto.add=duration_add_subtract__add,duration_prototype__proto.subtract=duration_add_subtract__subtract,duration_prototype__proto.as=as,duration_prototype__proto.asMilliseconds=asMilliseconds,duration_prototype__proto.asSeconds=asSeconds,duration_prototype__proto.asMinutes=asMinutes,duration_prototype__proto.asHours=asHours,duration_prototype__proto.asDays=asDays,duration_prototype__proto.asWeeks=asWeeks,duration_prototype__proto.asMonths=asMonths,duration_prototype__proto.asYears=asYears,duration_prototype__proto.valueOf=duration_as__valueOf,duration_prototype__proto._bubble=bubble,duration_prototype__proto.get=duration_get__get,duration_prototype__proto.milliseconds=milliseconds,duration_prototype__proto.seconds=seconds,duration_prototype__proto.minutes=minutes,duration_prototype__proto.hours=hours,duration_prototype__proto.days=days,duration_prototype__proto.weeks=weeks,duration_prototype__proto.months=months,duration_prototype__proto.years=years,duration_prototype__proto.humanize=humanize,duration_prototype__proto.toISOString=iso_string__toISOString,duration_prototype__proto.toString=iso_string__toISOString,duration_prototype__proto.toJSON=iso_string__toISOString,duration_prototype__proto.locale=locale,duration_prototype__proto.localeData=localeData,duration_prototype__proto.toIsoString=deprecate("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",iso_string__toISOString),duration_prototype__proto.lang=lang,addFormatToken("X",0,0,"unix"),addFormatToken("x",0,0,"valueOf"),addRegexToken("x",matchSigned),addRegexToken("X",matchTimestamp),addParseToken("X",function(input,array,config){config._d=new Date(1e3*parseFloat(input,10))}),addParseToken("x",function(input,array,config){config._d=new Date(toInt(input))}),utils_hooks__hooks.version="2.10.6",setHookCallback(local__createLocal),utils_hooks__hooks.fn=momentPrototype,utils_hooks__hooks.min=min,utils_hooks__hooks.max=max,utils_hooks__hooks.utc=create_utc__createUTC,utils_hooks__hooks.unix=moment__createUnix,utils_hooks__hooks.months=lists__listMonths,utils_hooks__hooks.isDate=isDate,utils_hooks__hooks.locale=locale_locales__getSetGlobalLocale,utils_hooks__hooks.invalid=valid__createInvalid,utils_hooks__hooks.duration=create__createDuration,utils_hooks__hooks.isMoment=isMoment,utils_hooks__hooks.weekdays=lists__listWeekdays,utils_hooks__hooks.parseZone=moment__createInZone,utils_hooks__hooks.localeData=locale_locales__getLocale,utils_hooks__hooks.isDuration=isDuration,utils_hooks__hooks.monthsShort=lists__listMonthsShort,utils_hooks__hooks.weekdaysMin=lists__listWeekdaysMin,utils_hooks__hooks.defineLocale=defineLocale,utils_hooks__hooks.weekdaysShort=lists__listWeekdaysShort,utils_hooks__hooks.normalizeUnits=normalizeUnits,utils_hooks__hooks.relativeTimeThreshold=duration_humanize__getSetRelativeTimeThreshold;var _moment=utils_hooks__hooks;return _moment}); + +},{}],27:[function(require,module,exports){ +(function(){var Type,builtin,def,defaults,geq,isBoolean,isNumber,isString,shared,types;types=require("ast-types/lib/types"),Type=types.Type,def=Type.def,builtin=types.builtInTypes,isString=builtin.string,isNumber=builtin.number,isBoolean=builtin["boolean"],shared=require("ast-types/lib/shared"),defaults=shared.defaults,geq=shared.geq,def("Printable").field("loc",Type.or(def("SourceLocation"),null),defaults["null"],!0),def("Node").bases("Printable").field("type",isString),def("SourceLocation").build("start","end","source").field("start",def("Position")).field("end",def("Position")).field("source",Type.or(isString,null),defaults["null"]),def("Position").build("line","column").field("line",geq(1)).field("column",geq(0)),def("Literal").bases("Node"),def("Boolean").bases("Literal").build("value").field("value",isBoolean),def("String").bases("Literal").build("value").field("value",isString),def("Number").bases("Literal").build("value").field("value",isNumber),def("Date").bases("Literal").build("value").field("value",isString),def("Expression").bases("Node"),def("BinaryExpression").bases("Expression").field("left",def("Expression")).field("right",def("Expression")),def("UnaryExpression").bases("Expression").field("expression",def("Expression")),def("AndExpression").bases("BinaryExpression").build("left","right"),def("OrExpression").bases("BinaryExpression").build("left","right"),def("NotExpression").bases("UnaryExpression").build("expression"),def("ParentesizedExpression").bases("UnaryExpression").build("expression"),def("BlockExpression").bases("UnaryExpression").build("expression"),def("Comparison").bases("Expression").build("operator","left","right").field("operator",Type.or("=",">=","<=","<",">","!=","~","!~")).field("left",def("IdentifierPath")).field("right",def("Literal")),def("Identifier").bases("Node").build("name").field("name",Type.or(isString,isNumber)),def("RegexpIdentifier").bases("Identifier").build("name"),def("IdentifierPath").bases("Node").build("components").field("components",[Type.or(def("Identifier"),null)]).field("regex",isBoolean,defaults["false"]),def("Query").bases("Node").build("expression").field("expression",Type.or(def("Expression"),null),defaults["null"]),def("Subquery").bases("Expression").build("endpoint","expression").field("endpoint",isString).field("expression",def("Expression")),def("Resource").bases("Expression").build("res_type","title","exported","parameters").field("res_type",isString).field("title",def("Identifier")).field("exported",isBoolean).field("parameters",Type.or(def("BlockExpression"),null),defaults["null"]).field("regex",isBoolean,defaults["false"]),def("RegexpNodeMatch").bases("Expression").build("value").field("value",def("IdentifierPath")),types.finalize(),exports.Type=types.Type,exports.builtInTypes=types.builtInTypes,exports.namedTypes=types.namedTypes,exports.builders=types.builders,exports.defineMethod=types.defineMethod,exports.getFieldNames=types.getFieldNames,exports.getFieldValue=types.getFieldValue,exports.eachField=types.eachField,exports.someField=types.someField,exports.getSupertypeNames=types.getSupertypeNames,exports.finalize=types.finalize,exports.NodePath=require("ast-types/lib/node-path"),exports.PathVisitor=require("ast-types/lib/path-visitor"),exports.visit=exports.PathVisitor.visit}).call(this); + +},{"ast-types/lib/node-path":32,"ast-types/lib/path-visitor":33,"ast-types/lib/shared":36,"ast-types/lib/types":37}],28:[function(require,module,exports){ +(function(){var evaluate,timespec,util,visit;visit=require("./ast").visit,util=require("./util"),timespec=require("timespec"),evaluate=function(ast){var mode;return mode=["fact"],visit(ast,{visitComparison:function(path){var comparison,left;return this.traverse(path),comparison=function(left,right){return"!="===path.node.operator||"!~"===path.node.operator?["not",[path.node.operator[1],left,right]]:[path.node.operator,left,right]},"fact"===mode[0]?["in","certname",["extract","certname",["select_fact_contents",["and",path.node.left,comparison("value",path.node.right)]]]]:"subquery"===mode[0]?(left=1===path.node.left.length?path.node.left[0]:path.node.left,comparison(left,path.node.right)):"resource"===mode[0]?comparison(["parameter",path.node.left[0]],path.node.right):void 0},visitBoolean:function(path){return path.replace(path.node.value),!1},visitString:function(path){return path.node.value},visitNumber:function(path){return path.node.value},visitDate:function(path){try{return timespec.parse(path.node.value).toISOString()}catch(_error){throw new Error('Failed to parse date: "'+path.node.value+'" at '+util.formatLocation(path.node))}},visitAndExpression:function(path){return this.traverse(path),["and",path.node.left,path.node.right]},visitOrExpression:function(path){return this.traverse(path),["or",path.node.left,path.node.right]},visitNotExpression:function(path){return this.traverse(path),["not",path.node.expression]},visitQuery:function(path){return this.traverse(path),path.node.expression},visitParentesizedExpression:function(path){return this.traverse(path),path.node.expression},visitBlockExpression:function(path){return this.traverse(path),path.node.expression},visitSubquery:function(path){return mode.unshift("subquery"),this.traverse(path),mode.shift(),["in","certname",["extract","certname",["select_"+path.node.endpoint+"s",path.node.expression]]]},visitRegexpNodeMatch:function(path){return mode.unshift("regexp"),this.traverse(path),mode.shift(),["~","certname",util.regexpEscape(path.node.value.join("."))]},visitIdentifierPath:function(path){return path.node.components.forEach(function(component){return"RegexpIdentifier"===component.type?path.node.regexp=!0:void 0}),this.traverse(path),"fact"===mode[0]?[path.node.regexp?"~>":"=","path",path.node.components]:path.node.components},visitRegexpIdentifier:function(path){return path.node.name},visitIdentifier:function(path){return path.parentPath.node.regexp?util.regexpEscape(path.node.name):path.node.name},visitResource:function(path){var and_expr,title;return"RegexpIdentifier"===path.node.title.type&&(path.node.regexp=!0),mode.unshift("resource"),this.traverse(path),mode.shift(),title=path.node.title,path.node.regexp||"Class"!==util.capitalize(path.node.res_type)||(title=util.capitalizeClass(title)),and_expr=["and",["=","type",util.capitalize(path.node.res_type)],[path.node.regexp?"~":"=","title",title],["=","exported",path.node.exported]],path.node.parameters&&and_expr.push(path.node.parameters),["in","certname",["extract","certname",["select_resources",and_expr]]]}})},exports.evaluate=evaluate}).call(this); + +},{"./ast":27,"./util":31,"timespec":38}],29:[function(require,module,exports){ +(function(){var evaluator,parser;parser=require("../build/parser.min"),evaluator=require("../build/evaluator"),exports.evaluator=evaluator,exports.parser=parser,exports.parse=function(query){var ast;return ast=parser.parse(query),evaluator.evaluate(ast)}}).call(this); + +},{"../build/evaluator":28,"../build/parser.min":30}],30:[function(require,module,exports){ +(function (process){ +var parser=function(){function Parser(){this.yy={}}var o=function(a,b,c,d){for(c=c||{},d=a.length;d--;c[a[d]]=b);return c},$V0=[1,5],$V1=[1,6],$V2=[1,15],$V3=[1,16],$V4=[1,13],$V5=[1,12],$V6=[1,18],$V7=[1,17],$V8=[1,20],$V9=[1,21],$Va=[5,8,9,11,36],$Vb=[1,24],$Vc=[1,25],$Vd=[1,26],$Ve=[1,27],$Vf=[1,28],$Vg=[1,29],$Vh=[1,30],$Vi=[1,31],$Vj=[1,22],$Vk=[5,8,9,11,22,23,24,25,26,27,28,29,32,36],$Vl=[2,25],$Vm=[5,8,9,11,22,23,24,25,26,27,28,29,32,36,38],$Vn=[2,39],$Vo=[20,40,41,42],$Vp=[1,55],parser={trace:function(){},yy:{},symbols_:{error:2,query:3,expression:4,EOF:5,identifier_path:6,not:7,and:8,or:9,"(":10,")":11,resource_expression:12,comparison_expression:13,subquery:14,literal:15,"boolean":16,string:17,integer:18,"float":19,"@":20,comparison_op:21,"~":22,"!~":23,"=":24,"!=":25,">":26,">=":27,"<":28,"<=":29,identifier:30,"*":31,".":32,"#":33,block_expression:34,"{":35,"}":36,"[":37,"]":38,"@@":39,Boolean:40,Number:41,String:42,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",7:"not",8:"and",9:"or",10:"(",11:")",20:"@",22:"~",23:"!~",24:"=",25:"!=",26:">",27:">=",28:"<",29:"<=",31:"*",32:".",33:"#",35:"{",36:"}",37:"[",38:"]",39:"@@",40:"Boolean",41:"Number",42:"String"},productions_:[0,[3,2],[3,1],[4,1],[4,2],[4,3],[4,3],[4,3],[4,1],[4,1],[4,1],[15,1],[15,1],[15,1],[15,1],[15,2],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[13,3],[30,1],[30,1],[30,2],[30,1],[6,1],[6,3],[14,4],[14,3],[34,3],[12,4],[12,5],[12,5],[12,6],[16,1],[18,1],[17,1],[19,3]],performAction:function(a,b,c,d,e,f,g){var h=f.length-1;switch(e){case 1:return this.$=ast.query(f[h-1]),this.$.loc=loc(this._$),this.$;case 2:return this.$=ast.query(),this.$.loc=loc(this._$),this.$;case 3:this.$=ast.regexpNodeMatch(f[h]),this.$.loc=loc(this._$);break;case 4:this.$=ast.notExpression(f[h]),this.$.loc=loc(this._$);break;case 5:this.$=ast.andExpression(f[h-2],f[h]),this.$.loc=loc(this._$);break;case 6:this.$=ast.orExpression(f[h-2],f[h]),this.$.loc=loc(this._$);break;case 7:this.$=ast.parentesizedExpression(f[h-1]),this.$.loc=loc(this._$);break;case 11:this.$=ast["boolean"](f[h]),this.$.loc=loc(this._$);break;case 12:this.$=ast.string(f[h]),this.$.loc=loc(this._$);break;case 13:case 14:this.$=ast.number(f[h]),this.$.loc=loc(this._$);break;case 15:this.$=ast.date(f[h]),this.$.loc=loc(this._$);break;case 24:this.$=ast.comparison(f[h-1],f[h-2],f[h]),this.$.loc=loc(this._$);break;case 25:case 26:this.$=ast.identifier(f[h]),this.$.loc=loc(this._$);break;case 27:this.$=ast.regexpIdentifier(f[h]),this.$.loc=loc(this._$);break;case 28:this.$=ast.regexpIdentifier(".*"),this.$.loc=loc(this._$);break;case 29:this.$=ast.identifierPath([f[h]]),this.$.loc=loc(this._$);break;case 30:f[h-2].components.push(f[h]),this.$=f[h-2],this.$.loc=loc(this._$);break;case 31:this.$=ast.subquery(f[h-2],f[h]),this.$.loc=loc(this._$);break;case 32:this.$=ast.subquery(f[h-1],f[h]),this.$.loc=loc(this._$);break;case 33:this.$=ast.blockExpression(f[h-1]),this.$.loc=loc(this._$);break;case 34:this.$=ast.resource(f[h-3],f[h-1],!1),this.$.loc=loc(this._$);break;case 35:this.$=ast.resource(f[h-4],f[h-2],!1,f[h]),this.$.loc=loc(this._$);break;case 36:this.$=ast.resource(f[h-3],f[h-1],!0),this.$.loc=loc(this._$);break;case 37:this.$=ast.resource(f[h-4],f[h-2],!0,f[h]),this.$.loc=loc(this._$);break;case 38:this.$="true"===a?!0:!1;break;case 39:this.$=parseInt(a,10);break;case 40:this.$=a;break;case 41:this.$=parseFloat(f[h-2]+"."+f[h])}},table:[{3:1,4:2,5:[1,3],6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},{1:[3]},{5:[1,19],8:$V8,9:$V9},{1:[2,2]},o($Va,[2,3],{21:23,22:$Vb,23:$Vc,24:$Vd,25:$Ve,26:$Vf,27:$Vg,28:$Vh,29:$Vi,32:$Vj}),{4:32,6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},{4:33,6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},o($Va,[2,8]),o($Va,[2,9]),o($Va,[2,10]),o($Vk,[2,29]),o($Vk,$Vl,{37:[1,34]}),{17:35,42:$V7},{17:36,42:$V7},o($Vm,[2,26]),{17:37,42:$V7},o($Vm,[2,28]),o([5,8,9,11,22,23,24,25,26,27,28,29,32,35,36,37,38],[2,40]),o($Vm,$Vn),{1:[2,1]},{4:38,6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},{4:39,6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},{17:41,18:14,22:$V2,30:40,31:$V3,41:$V6,42:$V7},{15:42,16:43,17:44,18:45,19:46,20:[1,47],40:[1,48],41:[1,49],42:$V7},o($Vo,[2,16]),o($Vo,[2,17]),o($Vo,[2,18]),o($Vo,[2,19]),o($Vo,[2,20]),o($Vo,[2,21]),o($Vo,[2,22]),o($Vo,[2,23]),o($Va,[2,4]),{8:$V8,9:$V9,11:[1,50]},{17:41,18:14,22:$V2,30:51,31:$V3,41:$V6,42:$V7},{37:[1,52]},{32:[1,53],34:54,35:$Vp},o($Vm,[2,27]),o($Va,[2,5]),o([5,9,11,36],[2,6],{8:$V8}),o($Vk,[2,30]),o($Vm,$Vl),o($Va,[2,24]),o($Va,[2,11]),o($Va,[2,12]),o($Va,[2,13]),o($Va,[2,14]),{17:56,42:$V7},o($Va,[2,38]),o($Va,$Vn,{32:[1,57]}),o($Va,[2,7]),{38:[1,58]},{17:41,18:14,22:$V2,30:59,31:$V3,41:$V6,42:$V7},{6:61,13:60,17:41,18:14,22:$V2,30:10,31:$V3,41:$V6,42:$V7},o($Va,[2,32]),{4:62,6:4,7:$V0,10:$V1,12:7,13:8,14:9,17:11,18:14,22:$V2,30:10,31:$V3,33:$V4,39:$V5,41:$V6,42:$V7},o($Va,[2,15]),{41:[1,63]},o($Va,[2,34],{34:64,35:$Vp}),{38:[1,65]},o($Va,[2,31]),{21:23,22:$Vb,23:$Vc,24:$Vd,25:$Ve,26:$Vf,27:$Vg,28:$Vh,29:$Vi,32:$Vj},{8:$V8,9:$V9,36:[1,66]},o($Va,[2,41]),o($Va,[2,35]),o($Va,[2,36],{34:67,35:$Vp}),o($Va,[2,33]),o($Va,[2,37])],defaultActions:{3:[2,2],19:[2,1]},parseError:function(a,b){if(!b.recoverable)throw new Error(a);this.trace(a)},parse:function(a){function b(){var a;return a=o.lex()||m,"number"!=typeof a&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h="",i=0,j=0,k=0,l=2,m=1,n=f.slice.call(arguments,1),o=Object.create(this.lexer),p={yy:{}};for(var q in this.yy)Object.prototype.hasOwnProperty.call(this.yy,q)&&(p.yy[q]=this.yy[q]);o.setInput(a,p.yy),p.yy.lexer=o,p.yy.parser=this,"undefined"==typeof o.yylloc&&(o.yylloc={});var r=o.yylloc;f.push(r);var s=o.options&&o.options.ranges;"function"==typeof p.yy.parseError?this.parseError=p.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var t,u,v,w,x,y,z,A,B,C={};;){if(v=d[d.length-1],this.defaultActions[v]?w=this.defaultActions[v]:((null===t||"undefined"==typeof t)&&(t=b()),w=g[v]&&g[v][t]),"undefined"==typeof w||!w.length||!w[0]){var D="";B=[];for(y in g[v])this.terminals_[y]&&y>l&&B.push("'"+this.terminals_[y]+"'");D=o.showPosition?"Parse error on line "+(i+1)+":\n"+o.showPosition()+"\nExpecting "+B.join(", ")+", got '"+(this.terminals_[t]||t)+"'":"Parse error on line "+(i+1)+": Unexpected "+(t==m?"end of input":"'"+(this.terminals_[t]||t)+"'"),this.parseError(D,{text:o.match,token:this.terminals_[t]||t,line:o.yylineno,loc:r,expected:B})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+v+", token: "+t);switch(w[0]){case 1:d.push(t),e.push(o.yytext),f.push(o.yylloc),d.push(w[1]),t=null,u?(t=u,u=null):(j=o.yyleng,h=o.yytext,i=o.yylineno,r=o.yylloc,k>0&&k--);break;case 2:if(z=this.productions_[w[1]][1],C.$=e[e.length-z],C._$={first_line:f[f.length-(z||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(z||1)].first_column,last_column:f[f.length-1].last_column},s&&(C._$.range=[f[f.length-(z||1)].range[0],f[f.length-1].range[1]]),x=this.performAction.apply(C,[h,j,i,p.yy,w[1],e,f].concat(n)),"undefined"!=typeof x)return x;z&&(d=d.slice(0,-1*z*2),e=e.slice(0,-1*z),f=f.slice(0,-1*z)),d.push(this.productions_[w[1]][0]),e.push(C.$),f.push(C._$),A=g[d[d.length-2]][d[d.length-1]],d.push(A);break;case 3:return!0}}return!0}},astlib=require("./ast"),ast=astlib.builders,evaluator=require("./evaluator"),loc=require("./util").loc,lexer=function(){var lexer={EOF:1,parseError:function(a,b){if(!this.yy.parser)throw new Error(a);this.yy.parser.parseError(a,b)},setInput:function(a,b){return this.yy=b||this.yy||{},this._input=a,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var b=a.match(/(?:\r\n?|\n).*/g);return b?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var b=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-b),this.offset-=b;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var e=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-b},this.options.ranges&&(this.yylloc.range=[e[0],e[0]+this.yyleng-b]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=new Array(a.length+1).join("-");return a+this.upcomingInput()+"\n"+b+"^"},test_match:function(a,b){var c,d,e;if(this.options.backtrack_lexer&&(e={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(e.yylloc.range=this.yylloc.range.slice(0))),d=a[0].match(/(?:\r\n?|\n).*/g),d&&(this.yylineno+=d.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:d?d[d.length-1].length-d[d.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+a[0].length},this.yytext+=a[0],this.match+=a[0],this.matches=a,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(a[0].length),this.matched+=a[0],c=this.performAction.call(this,this.yy,this,b,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),c)return c;if(this._backtrack){for(var f in e)this[f]=e[f];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d;this._more||(this.yytext="",this.match="");for(var e=this._currentRules(),f=0;fb[0].length)){if(b=c,d=f,this.options.backtrack_lexer){if(a=this.test_match(c,e[f]),a!==!1)return a;if(this._backtrack){b=!1;continue}return!1}if(!this.options.flex)break}return b?(a=this.test_match(b,e[d]),a!==!1?a:!1):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var a=this.next();return a?a:this.lex()},begin:function(a){this.conditionStack.push(a)},popState:function(){var a=this.conditionStack.length-1;return a>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(a){return a=this.conditionStack.length-1-Math.abs(a||0),a>=0?this.conditionStack[a]:"INITIAL"},pushState:function(a){this.begin(a)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function anonymous(yy,yy_,$avoiding_name_collisions,YY_START){var YYSTATE=YY_START;switch($avoiding_name_collisions){case 0:break;case 1:return 10;case 2:return 11;case 3:return 37;case 4:return 38;case 5:return 35;case 6:return 36;case 7:return 24;case 8:return 25;case 9:return 22;case 10:return 28;case 11:return 29;case 12:return 26;case 13:return 27;case 14:return 31;case 15:return 33;case 16:return 7;case 17:return 8;case 18:return 9;case 19:return 40;case 20:return 40;case 21:return 41;case 22:return yy_.yytext=eval(yy_.yytext),42;case 23:return yy_.yytext=eval(yy_.yytext),42;case 24:return 32;case 25:return 42;case 26:return 39;case 27:return 20;case 28:return 5}},rules:[/^(?:\s+)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:=)/,/^(?:!=)/,/^(?:~)/,/^(?:<)/,/^(?:<=)/,/^(?:>)/,/^(?:>=)/,/^(?:\*)/,/^(?:#)/,/^(?:not\b)/,/^(?:and\b)/,/^(?:or\b)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:-?\d+)/,/^(?:"(\\.|[^\\"])*")/,/^(?:'(\\.|[^\\'])*')/,/^(?:\.)/,/^(?:[-\w_:]+)/,/^(?:@@)/,/^(?:@)/,/^(?:$)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],inclusive:!0}}};return lexer}();return parser.lexer=lexer,Parser.prototype=parser,parser.Parser=Parser,new Parser}();"undefined"!=typeof require&&"undefined"!=typeof exports&&(exports.parser=parser,exports.Parser=parser.Parser,exports.parse=function(){return parser.parse.apply(parser,arguments)},exports.main=function(a){a[1]||(console.log("Usage: "+a[0]+" FILE"),process.exit(1));var b=require("fs").readFileSync(require("path").normalize(a[1]),"utf8");return exports.parser.parse(b)},"undefined"!=typeof module&&require.main===module&&exports.main(process.argv.slice(1))); + +}).call(this,require('_process')) +},{"./ast":27,"./evaluator":28,"./util":31,"_process":23,"fs":19,"path":22}],31:[function(require,module,exports){ +(function(){var ast;ast=require("../build/ast"),exports.capitalize=function(s){return s.charAt(0).toUpperCase()+s.slice(1).toLowerCase()},exports.capitalizeClass=function(s){return s.split("::").map(exports.capitalize).join("::")},exports.regexpEscape=function(s){return String(s).replace(/([-()\[\]{}+?*.$\^|,:#np)return!0;if(pp===np&&"right"===this.name)return assert.strictEqual(parent.right,node),!0;default:return!1}case"SequenceExpression":switch(parent.type){case"ForStatement":return!1;case"ExpressionStatement":return"expression"!==this.name;default:return!0}case"YieldExpression":switch(parent.type){case"BinaryExpression":case"LogicalExpression":case"UnaryExpression":case"SpreadElement":case"SpreadProperty":case"CallExpression":case"MemberExpression":case"NewExpression":case"ConditionalExpression":case"YieldExpression":return!0;default:return!1}case"Literal":return"MemberExpression"===parent.type&&isNumber.check(node.value)&&"object"===this.name&&parent.object===node;case"AssignmentExpression":case"ConditionalExpression":switch(parent.type){case"UnaryExpression":case"SpreadElement":case"SpreadProperty":case"BinaryExpression":case"LogicalExpression":return!0;case"CallExpression":return"callee"===this.name&&parent.callee===node;case"ConditionalExpression":return"test"===this.name&&parent.test===node;case"MemberExpression":return"object"===this.name&&parent.object===node;default:return!1}default:if("NewExpression"===parent.type&&"callee"===this.name&&parent.callee===node)return containsCallExpression(node)}return assumeExpressionContext!==!0&&!this.canBeFirstInStatement()&&this.firstInStatement()?!0:!1};var PRECEDENCE={};[["||"],["&&"],["|"],["^"],["&"],["==","===","!=","!=="],["<",">","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]].forEach(function(tier,i){tier.forEach(function(op){PRECEDENCE[op]=i})}),NPp.canBeFirstInStatement=function(){var node=this.node;return!n.FunctionExpression.check(node)&&!n.ObjectExpression.check(node)},NPp.firstInStatement=function(){return firstInStatement(this)},module.exports=NodePath; + +},{"./path":34,"./scope":35,"./types":37,"assert":20,"util":25}],33:[function(require,module,exports){ +function PathVisitor(){assert.ok(this instanceof PathVisitor),this._reusableContextStack=[],this._methodNameTable=computeMethodNameTable(this),this._shouldVisitComments=hasOwn.call(this._methodNameTable,"Block")||hasOwn.call(this._methodNameTable,"Line"),this.Context=makeContextConstructor(this),this._visiting=!1,this._changeReported=!1}function computeMethodNameTable(visitor){var typeNames=Object.create(null);for(var methodName in visitor)/^visit[A-Z]/.test(methodName)&&(typeNames[methodName.slice("visit".length)]=!0);for(var supertypeTable=types.computeSupertypeLookupTable(typeNames),methodNameTable=Object.create(null),typeNames=Object.keys(supertypeTable),typeNameCount=typeNames.length,i=0;typeNameCount>i;++i){var typeName=typeNames[i];methodName="visit"+supertypeTable[typeName],isFunction.check(visitor[methodName])&&(methodNameTable[typeName]=methodName)}return methodNameTable}function extend(target,source){for(var property in source)hasOwn.call(source,property)&&(target[property]=source[property]);return target}function visitChildren(path,visitor){assert.ok(path instanceof NodePath),assert.ok(visitor instanceof PathVisitor);var value=path.value;if(isArray.check(value))path.each(visitor.visitWithoutReset,visitor);else if(isObject.check(value)){var childNames=types.getFieldNames(value);visitor._shouldVisitComments&&value.comments&&childNames.indexOf("comments")<0&&childNames.push("comments");for(var childCount=childNames.length,childPaths=[],i=0;childCount>i;++i){var childName=childNames[i];hasOwn.call(value,childName)||(value[childName]=types.getFieldValue(value,childName)),childPaths.push(path.get(childName))}for(var i=0;childCount>i;++i)visitor.visitWithoutReset(childPaths[i])}else;return path.value}function makeContextConstructor(visitor){function Context(path){assert.ok(this instanceof Context),assert.ok(this instanceof PathVisitor),assert.ok(path instanceof NodePath),Object.defineProperty(this,"visitor",{value:visitor,writable:!1,enumerable:!0,configurable:!1}),this.currentPath=path,this.needToCallTraverse=!0,Object.seal(this)}assert.ok(visitor instanceof PathVisitor);var Cp=Context.prototype=Object.create(visitor);return Cp.constructor=Context,extend(Cp,sharedContextProtoMethods),Context}var assert=require("assert"),types=require("./types"),NodePath=require("./node-path"),Printable=types.namedTypes.Printable,isArray=types.builtInTypes.array,isObject=types.builtInTypes.object,isFunction=types.builtInTypes["function"],hasOwn=Object.prototype.hasOwnProperty,undefined;PathVisitor.fromMethodsObject=function(methods){function Visitor(){assert.ok(this instanceof Visitor),PathVisitor.call(this)}if(methods instanceof PathVisitor)return methods;if(!isObject.check(methods))return new PathVisitor;var Vp=Visitor.prototype=Object.create(PVp);return Vp.constructor=Visitor,extend(Vp,methods),extend(Visitor,PathVisitor),isFunction.assert(Visitor.fromMethodsObject),isFunction.assert(Visitor.visit),new Visitor},PathVisitor.visit=function(node,methods){return PathVisitor.fromMethodsObject(methods).visit(node)};var PVp=PathVisitor.prototype,recursiveVisitWarning=["Recursively calling visitor.visit(path) resets visitor state.","Try this.visit(path) or this.traverse(path) instead."].join(" ");PVp.visit=function(){assert.ok(!this._visiting,recursiveVisitWarning),this._visiting=!0,this._changeReported=!1,this._abortRequested=!1;for(var argc=arguments.length,args=new Array(argc),i=0;argc>i;++i)args[i]=arguments[i];args[0]instanceof NodePath||(args[0]=new NodePath({root:args[0]}).get("root")),this.reset.apply(this,args);try{var root=this.visitWithoutReset(args[0]),didNotThrow=!0}finally{if(this._visiting=!1,!didNotThrow&&this._abortRequested)return args[0].value}return root},PVp.AbortRequest=function(){},PVp.abort=function(){var visitor=this;visitor._abortRequested=!0;var request=new visitor.AbortRequest;throw request.cancel=function(){visitor._abortRequested=!1},request},PVp.reset=function(path){},PVp.visitWithoutReset=function(path){if(this instanceof this.Context)return this.visitor.visitWithoutReset(path);assert.ok(path instanceof NodePath);var value=path.value,methodName=Printable.check(value)&&this._methodNameTable[value.type];if(!methodName)return visitChildren(path,this);var context=this.acquireContext(path);try{return context.invokeVisitorMethod(methodName)}finally{this.releaseContext(context)}},PVp.acquireContext=function(path){return 0===this._reusableContextStack.length?new this.Context(path):this._reusableContextStack.pop().reset(path)},PVp.releaseContext=function(context){assert.ok(context instanceof this.Context),this._reusableContextStack.push(context),context.currentPath=null},PVp.reportChanged=function(){this._changeReported=!0},PVp.wasChangeReported=function(){return this._changeReported};var sharedContextProtoMethods=Object.create(null);sharedContextProtoMethods.reset=function(path){return assert.ok(this instanceof this.Context),assert.ok(path instanceof NodePath),this.currentPath=path,this.needToCallTraverse=!0,this},sharedContextProtoMethods.invokeVisitorMethod=function(methodName){assert.ok(this instanceof this.Context),assert.ok(this.currentPath instanceof NodePath);var result=this.visitor[methodName].call(this,this.currentPath);result===!1?this.needToCallTraverse=!1:result!==undefined&&(this.currentPath=this.currentPath.replace(result)[0],this.needToCallTraverse&&this.traverse(this.currentPath)),assert.strictEqual(this.needToCallTraverse,!1,"Must either call this.traverse or return false in "+methodName);var path=this.currentPath;return path&&path.value},sharedContextProtoMethods.traverse=function(path,newVisitor){return assert.ok(this instanceof this.Context),assert.ok(path instanceof NodePath),assert.ok(this.currentPath instanceof NodePath),this.needToCallTraverse=!1,visitChildren(path,PathVisitor.fromMethodsObject(newVisitor||this.visitor))},sharedContextProtoMethods.visit=function(path,newVisitor){return assert.ok(this instanceof this.Context),assert.ok(path instanceof NodePath),assert.ok(this.currentPath instanceof NodePath),this.needToCallTraverse=!1,PathVisitor.fromMethodsObject(newVisitor||this.visitor).visitWithoutReset(path)},sharedContextProtoMethods.reportChanged=function(){this.visitor.reportChanged()},sharedContextProtoMethods.abort=function(){this.needToCallTraverse=!1,this.visitor.abort()},module.exports=PathVisitor; + +},{"./node-path":32,"./types":37,"assert":20}],34:[function(require,module,exports){ +function Path(value,parentPath,name){assert.ok(this instanceof Path),parentPath?assert.ok(parentPath instanceof Path):(parentPath=null,name=null),this.value=value,this.parentPath=parentPath,this.name=name,this.__childCache=null}function getChildCache(path){return path.__childCache||(path.__childCache=Object.create(null))}function getChildPath(path,name){var cache=getChildCache(path),actualChildValue=path.getValueProperty(name),childPath=cache[name];return hasOwn.call(cache,name)&&childPath.value===actualChildValue||(childPath=cache[name]=new path.constructor(actualChildValue,path,name)),childPath}function emptyMoves(){}function getMoves(path,offset,start,end){if(isArray.assert(path.value),0===offset)return emptyMoves;var length=path.value.length;if(1>length)return emptyMoves;var argc=arguments.length;2===argc?(start=0,end=length):3===argc?(start=Math.max(start,0),end=length):(start=Math.max(start,0),end=Math.min(end,length)),isNumber.assert(start),isNumber.assert(end);for(var moves=Object.create(null),cache=getChildCache(path),i=start;end>i;++i)if(hasOwn.call(path.value,i)){var childPath=path.get(i);assert.strictEqual(childPath.name,i);var newIndex=i+offset;childPath.name=newIndex,moves[newIndex]=childPath,delete cache[i]}return delete cache.length,function(){for(var newIndex in moves){var childPath=moves[newIndex];assert.strictEqual(childPath.name,+newIndex),cache[newIndex]=childPath,path.value[newIndex]=childPath.value}}}function repairRelationshipWithParent(path){assert.ok(path instanceof Path);var pp=path.parentPath;if(!pp)return path;var parentValue=pp.value,parentCache=getChildCache(pp);if(parentValue[path.name]===path.value)parentCache[path.name]=path;else if(isArray.check(parentValue)){var i=parentValue.indexOf(path.value);i>=0&&(parentCache[path.name=i]=path)}else parentValue[path.name]=path.value,parentCache[path.name]=path;return assert.strictEqual(parentValue[path.name],path.value),assert.strictEqual(path.parentPath.get(path.name),path),path}var assert=require("assert"),Op=Object.prototype,hasOwn=Op.hasOwnProperty,types=require("./types"),isArray=types.builtInTypes.array,isNumber=types.builtInTypes.number,Ap=Array.prototype,slice=Ap.slice,map=Ap.map,Pp=Path.prototype;Pp.getValueProperty=function(name){return this.value[name]},Pp.get=function(name){for(var path=this,names=arguments,count=names.length,i=0;count>i;++i)path=getChildPath(path,names[i]);return path},Pp.each=function(callback,context){for(var childPaths=[],len=this.value.length,i=0,i=0;len>i;++i)hasOwn.call(this.value,i)&&(childPaths[i]=this.get(i));for(context=context||this,i=0;len>i;++i)hasOwn.call(childPaths,i)&&callback.call(context,childPaths[i])},Pp.map=function(callback,context){var result=[];return this.each(function(childPath){result.push(callback.call(this,childPath))},context),result},Pp.filter=function(callback,context){var result=[];return this.each(function(childPath){callback.call(this,childPath)&&result.push(childPath)},context),result},Pp.shift=function(){var move=getMoves(this,-1),result=this.value.shift();return move(),result},Pp.unshift=function(node){var move=getMoves(this,arguments.length),result=this.value.unshift.apply(this.value,arguments);return move(),result},Pp.push=function(node){return isArray.assert(this.value),delete getChildCache(this).length,this.value.push.apply(this.value,arguments)},Pp.pop=function(){isArray.assert(this.value);var cache=getChildCache(this);return delete cache[this.value.length-1],delete cache.length,this.value.pop()},Pp.insertAt=function(index,node){var argc=arguments.length,move=getMoves(this,argc-1,index);if(move===emptyMoves)return this;index=Math.max(index,0);for(var i=1;argc>i;++i)this.value[index+i-1]=arguments[i];return move(),this},Pp.insertBefore=function(node){for(var pp=this.parentPath,argc=arguments.length,insertAtArgs=[this.name],i=0;argc>i;++i)insertAtArgs.push(arguments[i]);return pp.insertAt.apply(pp,insertAtArgs)},Pp.insertAfter=function(node){for(var pp=this.parentPath,argc=arguments.length,insertAtArgs=[this.name+1],i=0;argc>i;++i)insertAtArgs.push(arguments[i]);return pp.insertAt.apply(pp,insertAtArgs)},Pp.replace=function(replacement){var results=[],parentValue=this.parentPath.value,parentCache=getChildCache(this.parentPath),count=arguments.length;if(repairRelationshipWithParent(this),isArray.check(parentValue)){for(var originalLength=parentValue.length,move=getMoves(this.parentPath,count-1,this.name+1),spliceArgs=[this.name,1],i=0;count>i;++i)spliceArgs.push(arguments[i]);var splicedOut=parentValue.splice.apply(parentValue,spliceArgs);if(assert.strictEqual(splicedOut[0],this.value),assert.strictEqual(parentValue.length,originalLength-1+count),move(),0===count)delete this.value,delete parentCache[this.name],this.__childCache=null;else{for(assert.strictEqual(parentValue[this.name],replacement),this.value!==replacement&&(this.value=replacement,this.__childCache=null),i=0;count>i;++i)results.push(this.parentPath.get(this.name+i));assert.strictEqual(results[0],this)}}else 1===count?(this.value!==replacement&&(this.__childCache=null),this.value=parentValue[this.name]=replacement,results.push(this)):0===count?(delete parentValue[this.name],delete this.value,this.__childCache=null):assert.ok(!1,"Could not replace path");return results},module.exports=Path; + +},{"./types":37,"assert":20}],35:[function(require,module,exports){ +function Scope(path,parentScope){assert.ok(this instanceof Scope),assert.ok(path instanceof require("./node-path")),ScopeType.assert(path.value);var depth;parentScope?(assert.ok(parentScope instanceof Scope),depth=parentScope.depth+1):(parentScope=null,depth=0),Object.defineProperties(this,{path:{value:path},node:{value:path.value},isGlobal:{value:!parentScope,enumerable:!0},depth:{value:depth},parent:{value:parentScope},bindings:{value:{}}})}function scanScope(path,bindings){var node=path.value;ScopeType.assert(node),namedTypes.CatchClause.check(node)?addPattern(path.get("param"),bindings):recursiveScanScope(path,bindings)}function recursiveScanScope(path,bindings){var node=path.value;path.parent&&namedTypes.FunctionExpression.check(path.parent.node)&&path.parent.node.id&&addPattern(path.parent.get("id"),bindings),node&&(isArray.check(node)?path.each(function(childPath){recursiveScanChild(childPath,bindings)}):namedTypes.Function.check(node)?(path.get("params").each(function(paramPath){addPattern(paramPath,bindings)}),recursiveScanChild(path.get("body"),bindings)):namedTypes.VariableDeclarator.check(node)?(addPattern(path.get("id"),bindings),recursiveScanChild(path.get("init"),bindings)):"ImportSpecifier"===node.type||"ImportNamespaceSpecifier"===node.type||"ImportDefaultSpecifier"===node.type?addPattern(path.get(node.local?"local":node.name?"name":"id"),bindings):Node.check(node)&&!Expression.check(node)&&types.eachField(node,function(name,child){var childPath=path.get(name);assert.strictEqual(childPath.value,child),recursiveScanChild(childPath,bindings)}))}function recursiveScanChild(path,bindings){var node=path.value;if(!node||Expression.check(node));else if(namedTypes.FunctionDeclaration.check(node))addPattern(path.get("id"),bindings);else if(namedTypes.ClassDeclaration&&namedTypes.ClassDeclaration.check(node))addPattern(path.get("id"),bindings);else if(ScopeType.check(node)){if(namedTypes.CatchClause.check(node)){var catchParamName=node.param.name,hadBinding=hasOwn.call(bindings,catchParamName);recursiveScanScope(path.get("body"),bindings),hadBinding||delete bindings[catchParamName]}}else recursiveScanScope(path,bindings)}function addPattern(patternPath,bindings){var pattern=patternPath.value;namedTypes.Pattern.assert(pattern),namedTypes.Identifier.check(pattern)?hasOwn.call(bindings,pattern.name)?bindings[pattern.name].push(patternPath):bindings[pattern.name]=[patternPath]:namedTypes.ObjectPattern&&namedTypes.ObjectPattern.check(pattern)?patternPath.get("properties").each(function(propertyPath){var property=propertyPath.value;namedTypes.Pattern.check(property)?addPattern(propertyPath,bindings):namedTypes.Property.check(property)?addPattern(propertyPath.get("value"),bindings):namedTypes.SpreadProperty&&namedTypes.SpreadProperty.check(property)&&addPattern(propertyPath.get("argument"),bindings)}):namedTypes.ArrayPattern&&namedTypes.ArrayPattern.check(pattern)?patternPath.get("elements").each(function(elementPath){var element=elementPath.value;namedTypes.Pattern.check(element)?addPattern(elementPath,bindings):namedTypes.SpreadElement&&namedTypes.SpreadElement.check(element)&&addPattern(elementPath.get("argument"),bindings)}):namedTypes.PropertyPattern&&namedTypes.PropertyPattern.check(pattern)?addPattern(patternPath.get("pattern"),bindings):(namedTypes.SpreadElementPattern&&namedTypes.SpreadElementPattern.check(pattern)||namedTypes.SpreadPropertyPattern&&namedTypes.SpreadPropertyPattern.check(pattern))&&addPattern(patternPath.get("argument"),bindings)}var assert=require("assert"),types=require("./types"),Type=types.Type,namedTypes=types.namedTypes,Node=namedTypes.Node,Expression=namedTypes.Expression,isArray=types.builtInTypes.array,hasOwn=Object.prototype.hasOwnProperty,b=types.builders,scopeTypes=[namedTypes.Program,namedTypes.Function,namedTypes.CatchClause],ScopeType=Type.or.apply(Type,scopeTypes);Scope.isEstablishedBy=function(node){return ScopeType.check(node)};var Sp=Scope.prototype;Sp.didScan=!1,Sp.declares=function(name){return this.scan(),hasOwn.call(this.bindings,name)},Sp.declareTemporary=function(prefix){prefix?assert.ok(/^[a-z$_]/i.test(prefix),prefix):prefix="t$",prefix+=this.depth.toString(36)+"$",this.scan();for(var index=0;this.declares(prefix+index);)++index;var name=prefix+index;return this.bindings[name]=types.builders.identifier(name)},Sp.injectTemporary=function(identifier,init){identifier||(identifier=this.declareTemporary());var bodyPath=this.path.get("body");return namedTypes.BlockStatement.check(bodyPath.value)&&(bodyPath=bodyPath.get("body")),bodyPath.unshift(b.variableDeclaration("var",[b.variableDeclarator(identifier,init||null)])),identifier},Sp.scan=function(force){if(force||!this.didScan){for(var name in this.bindings)delete this.bindings[name];scanScope(this.path,this.bindings),this.didScan=!0}},Sp.getBindings=function(){return this.scan(),this.bindings},Sp.lookup=function(name){for(var scope=this;scope&&!scope.declares(name);scope=scope.parent);return scope},Sp.getGlobalScope=function(){for(var scope=this;!scope.isGlobal;)scope=scope.parent;return scope},module.exports=Scope; + +},{"./node-path":32,"./types":37,"assert":20}],36:[function(require,module,exports){ +var types=require("../lib/types"),Type=types.Type,builtin=types.builtInTypes,isNumber=builtin.number;exports.geq=function(than){return new Type(function(value){return isNumber.check(value)&&value>=than},isNumber+" >= "+than)},exports.defaults={"null":function(){return null},emptyArray:function(){return[]},"false":function(){return!1},"true":function(){return!0},undefined:function(){}};var naiveIsPrimitive=Type.or(builtin.string,builtin.number,builtin["boolean"],builtin["null"],builtin.undefined);exports.isPrimitive=new Type(function(value){if(null===value)return!0;var type=typeof value;return!("object"===type||"function"===type)},naiveIsPrimitive.toString()); + +},{"../lib/types":37}],37:[function(require,module,exports){ +function Type(check,name){var self=this;assert.ok(self instanceof Type,self),assert.strictEqual(objToStr.call(check),funObjStr,check+" is not a function");var nameObjStr=objToStr.call(name);assert.ok(nameObjStr===funObjStr||nameObjStr===strObjStr,name+" is neither a function nor a string"),Object.defineProperties(self,{name:{value:name},check:{value:function(value,deep){var result=check.call(self,value,deep);return!result&&deep&&objToStr.call(deep)===funObjStr&&deep(self,value),result}}})}function shallowStringify(value){return isObject.check(value)?"{"+Object.keys(value).map(function(key){return key+": "+value[key]}).join(", ")+"}":isArray.check(value)?"["+value.map(shallowStringify).join(", ")+"]":JSON.stringify(value)}function defBuiltInType(example,name){var objStr=objToStr.call(example);return Object.defineProperty(builtInTypes,name,{enumerable:!0,value:new Type(function(value){return objToStr.call(value)===objStr},name)}),builtInTypes[name]}function toType(from,name){return from instanceof Type?from:from instanceof Def?from.type:isArray.check(from)?Type.fromArray(from):isObject.check(from)?Type.fromObject(from):isFunction.check(from)?new Type(from,name):new Type(function(value){return value===from},isUndefined.check(name)?function(){return from+""}:name)}function Field(name,type,defaultFn,hidden){var self=this;assert.ok(self instanceof Field),isString.assert(name),type=toType(type);var properties={name:{value:name},type:{value:type},hidden:{value:!!hidden}};isFunction.check(defaultFn)&&(properties.defaultFn={value:defaultFn}),Object.defineProperties(self,properties)}function Def(typeName){var self=this;assert.ok(self instanceof Def),Object.defineProperties(self,{typeName:{value:typeName},baseNames:{value:[]},ownFields:{value:Object.create(null)},allSupertypes:{value:Object.create(null)},supertypeList:{value:[]},allFields:{value:Object.create(null)},fieldNames:{value:[]},type:{value:new Type(function(value,deep){return self.check(value,deep)},typeName)}})}function getBuilderName(typeName){return typeName.replace(/^[A-Z]+/,function(upperCasePrefix){var len=upperCasePrefix.length;switch(len){case 0:return"";case 1:return upperCasePrefix.toLowerCase();default:return upperCasePrefix.slice(0,len-1).toLowerCase()+upperCasePrefix.charAt(len-1)}})}function getStatementBuilderName(typeName){return typeName=getBuilderName(typeName),typeName.replace(/(Expression)?$/,"Statement")}function getFieldNames(object){var d=Def.fromValue(object);return d?d.fieldNames.slice(0):("type"in object&&assert.ok(!1,"did not recognize object of type "+JSON.stringify(object.type)),Object.keys(object))}function getFieldValue(object,fieldName){var d=Def.fromValue(object);if(d){var field=d.allFields[fieldName];if(field)return field.getValue(object)}return object[fieldName]}function wrapExpressionBuilderWithStatement(typeName){var wrapperName=getStatementBuilderName(typeName);if(!builders[wrapperName]){var wrapped=builders[getBuilderName(typeName)];wrapped&&(builders[wrapperName]=function(){return builders.expressionStatement(wrapped.apply(builders,arguments))})}}function populateSupertypeList(typeName,list){list.length=0,list.push(typeName);for(var lastSeen=Object.create(null),pos=0;posfrom;++from)hasOwn.call(list,from)&&(list[to++]=list[from]);list.length=to}function extend(into,from){return Object.keys(from).forEach(function(name){into[name]=from[name]}),into}var assert=require("assert"),Ap=Array.prototype,slice=Ap.slice,map=Ap.map,each=Ap.forEach,Op=Object.prototype,objToStr=Op.toString,funObjStr=objToStr.call(function(){}),strObjStr=objToStr.call(""),hasOwn=Op.hasOwnProperty,Tp=Type.prototype;exports.Type=Type,Tp.assert=function(value,deep){if(!this.check(value,deep)){var str=shallowStringify(value);return assert.ok(!1,str+" does not match type "+this),!1}return!0},Tp.toString=function(){var name=this.name;return isString.check(name)?name:isFunction.check(name)?name.call(this)+"":name+" type"};var builtInTypes={};exports.builtInTypes=builtInTypes;var isString=defBuiltInType("","string"),isFunction=defBuiltInType(function(){},"function"),isArray=defBuiltInType([],"array"),isObject=defBuiltInType({},"object"),isRegExp=defBuiltInType(/./,"RegExp"),isDate=defBuiltInType(new Date,"Date"),isNumber=defBuiltInType(3,"number"),isBoolean=defBuiltInType(!0,"boolean"),isNull=defBuiltInType(null,"null"),isUndefined=defBuiltInType(void 0,"undefined");Type.or=function(){for(var types=[],len=arguments.length,i=0;len>i;++i)types.push(toType(arguments[i]));return new Type(function(value,deep){for(var i=0;len>i;++i)if(types[i].check(value,deep))return!0;return!1},function(){return types.join(" | ")})},Type.fromArray=function(arr){return assert.ok(isArray.check(arr)),assert.strictEqual(arr.length,1,"only one element type is permitted for typed arrays"),toType(arr[0]).arrayOf()},Tp.arrayOf=function(){var elemType=this;return new Type(function(value,deep){return isArray.check(value)&&value.every(function(elem){return elemType.check(elem,deep)})},function(){return"["+elemType+"]"})},Type.fromObject=function(obj){var fields=Object.keys(obj).map(function(name){return new Field(name,obj[name])});return new Type(function(value,deep){return isObject.check(value)&&fields.every(function(field){return field.type.check(value[field.name],deep)})},function(){return"{ "+fields.join(", ")+" }"})};var Fp=Field.prototype;Fp.toString=function(){return JSON.stringify(this.name)+": "+this.type},Fp.getValue=function(obj){var value=obj[this.name];return isUndefined.check(value)?(this.defaultFn&&(value=this.defaultFn.call(obj)),value):value},Type.def=function(typeName){return isString.assert(typeName),hasOwn.call(defCache,typeName)?defCache[typeName]:defCache[typeName]=new Def(typeName)};var defCache=Object.create(null);Def.fromValue=function(value){if(value&&"object"==typeof value){var type=value.type;if("string"==typeof type&&hasOwn.call(defCache,type)){var d=defCache[type];if(d.finalized)return d}}return null};var Dp=Def.prototype;Dp.isSupertypeOf=function(that){return that instanceof Def?(assert.strictEqual(this.finalized,!0),assert.strictEqual(that.finalized,!0),hasOwn.call(that.allSupertypes,this.typeName)):void assert.ok(!1,that+" is not a Def")},exports.getSupertypeNames=function(typeName){assert.ok(hasOwn.call(defCache,typeName));var d=defCache[typeName];return assert.strictEqual(d.finalized,!0),d.supertypeList.slice(1)},exports.computeSupertypeLookupTable=function(candidates){for(var table={},typeNames=Object.keys(defCache),typeNameCount=typeNames.length,i=0;typeNameCount>i;++i){var typeName=typeNames[i],d=defCache[typeName];assert.strictEqual(d.finalized,!0);for(var j=0;ji)value=args[i];else if(field.defaultFn)value=field.defaultFn.call(built);else{var message="no value or default function given for field "+JSON.stringify(param)+" of "+self.typeName+"("+self.buildParams.map(function(name){return all[name]}).join(", ")+")";assert.ok(!1,message)}type.check(value)||assert.ok(!1,shallowStringify(value)+" does not match field "+field+" of type "+self.typeName),built[param]=value}}var args=arguments,argc=args.length,built=Object.create(nodePrototype);return assert.ok(self.finalized,"attempting to instantiate unfinalized type "+self.typeName),self.buildParams.forEach(function(param,i){add(param,i)}),Object.keys(self.allFields).forEach(function(param){add(param)}),assert.strictEqual(built.type,self.typeName),built}}),self)},exports.getBuilderName=getBuilderName,exports.getStatementBuilderName=getStatementBuilderName,Dp.field=function(name,type,defaultFn,hidden){return assert.strictEqual(this.finalized,!1),this.ownFields[name]=new Field(name,type,defaultFn,hidden),this};var namedTypes={};exports.namedTypes=namedTypes,exports.getFieldNames=getFieldNames,exports.getFieldValue=getFieldValue,exports.eachField=function(object,callback,context){getFieldNames(object).forEach(function(name){callback.call(this,name,getFieldValue(object,name))},context)},exports.someField=function(object,callback,context){return getFieldNames(object).some(function(name){return callback.call(this,name,getFieldValue(object,name))},context)},Object.defineProperty(Dp,"finalized",{value:!1}),Dp.finalize=function(){if(!this.finalized){var allFields=this.allFields,allSupertypes=this.allSupertypes;this.baseNames.forEach(function(name){var def=defCache[name];def.finalize(),extend(allFields,def.allFields),extend(allSupertypes,def.allSupertypes)}),extend(allFields,this.ownFields),allSupertypes[this.typeName]=this,this.fieldNames.length=0;for(var fieldName in allFields)hasOwn.call(allFields,fieldName)&&!allFields[fieldName].hidden&&this.fieldNames.push(fieldName);Object.defineProperty(namedTypes,this.typeName,{enumerable:!0,value:this.type}),Object.defineProperty(this,"finalized",{value:!0}),populateSupertypeList(this.typeName,this.supertypeList),this.buildable&&this.supertypeList.lastIndexOf("Expression")>=0&&wrapExpressionBuilderWithStatement(this.typeName)}},exports.finalize=function(){Object.keys(defCache).forEach(function(name){defCache[name].finalize()})}; + +},{"assert":20}],38:[function(require,module,exports){ +module.exports=function(){function quote(s){return'"'+s.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g,escape)+'"'}var result={parse:function(input,startRule){function matchFailed(failure){rightmostFailuresPos>pos||(pos>rightmostFailuresPos&&(rightmostFailuresPos=pos,rightmostFailuresExpected=[]),rightmostFailuresExpected.push(failure))}function parse_timespec(){var result0,result1,result2,pos0,pos1,pos2;return pos0=pos,pos1=pos,result0=parse_spec_base(),null!==result0?(pos2=pos,result1=parse__(),null!==result1?(result2=parse_inc_or_dec(),null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,base,tail){if("object"==typeof tail){var count=tail[1].count,amount=tail[1].amount;base.setFullYear(base.getFullYear()+amount.y*count),base.setMonth(base.getMonth()+amount.m*count),base.setDate(base.getDate()+amount.d*count),base.setHours(base.getHours()+amount.h*count),base.setMinutes(base.getMinutes()+amount.i*count)}return base}(pos0,result0[0],result0[1])),null===result0&&(pos=pos0),result0}function parse_spec_base(){var result0,result1,result2,result3,pos0,pos1,pos2,pos3;return result0=parse_date(),null===result0&&(pos0=pos,pos1=pos,result0=parse_time_base(),null!==result0?(pos2=pos,pos3=pos,result1=parse__(),null!==result1?(result2=parse_timezone_name(),null!==result2?result1=[result1,result2]:(result1=null,pos=pos3)):(result1=null,pos=pos3),result1=null!==result1?result1:"",null!==result1?(pos3=pos,result2=parse__(),null!==result2?(result3=parse_date(),null!==result3?result2=[result2,result3]:(result2=null,pos=pos3)):(result2=null,pos=pos3),result2=null!==result2?result2:"",null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,t,tail){var utc=!1,date=null;if("object"==typeof tail&&("object"==typeof tail[0]&&(utc=!0),"object"==typeof tail[1]&&(date=tail[1][1])),utc&&(t=new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes(),t.getSeconds(),t.getMilliseconds()))),date)utc?(t.setUTCFullYear(date.getFullYear()),t.setUTCMonth(date.getMonth()),t.setUTCDate(date.getDate())):(t.setFullYear(date.getFullYear()),t.setMonth(date.getMonth()),t.setDate(date.getDate()));else{var now=new Date;now>t&&t.setDate(t.getDate()+1)}return t}(pos0,result0[0],result0[1])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"now"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"NOW"')),null!==result0&&(result0=function(offset){return new Date}(pos0)),null===result0&&(pos=pos0))),result0}function parse_time_base(){var result0,result1,result2,pos0,pos1,pos2;return result0=parse_hr24clock_hr_min(),null===result0&&(pos0=pos,pos1=pos,result0=parse_time_hour(),null!==result0?(result1=parse__(),null!==result1?(result2=parse_am_pm(),null!==result2?result0=[result0,result1,result2]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,t,offset){return t.setHours(t.getHours()+offset),t}(pos0,result0[0],result0[2])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,result0=parse_time_hour_min(),null!==result0?(pos2=pos,result1=parse__(),null!==result1?(result2=parse_am_pm(),null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,t,tail){return"object"==typeof tail&&t.setHours(t.getHours()+tail[1]),t}(pos0,result0[0],result0[1])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"noon"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"NOON"')),null!==result0&&(result0=function(offset){var today=new Date;return today.setHours(12),today.setMinutes(0),today.setSeconds(0),today.setMilliseconds(0),today}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"midnight"===input.substr(pos,8).toLowerCase()?(result0=input.substr(pos,8),pos+=8):(result0=null,0===reportFailures&&matchFailed('"MIDNIGHT"')),null!==result0&&(result0=function(offset){var today=new Date;return today.setHours(0),today.setMinutes(0),today.setSeconds(0),today.setMilliseconds(0),today}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"teatime"===input.substr(pos,7).toLowerCase()?(result0=input.substr(pos,7),pos+=7):(result0=null,0===reportFailures&&matchFailed('"TEATIME"')),null!==result0&&(result0=function(offset){var today=new Date;return today.setHours(16),today.setMinutes(0),today.setSeconds(0),today.setMilliseconds(0),today}(pos0)),null===result0&&(pos=pos0)))))),result0}function parse_hr24clock_hr_min(){var result0,result1,result2,pos0,pos1,pos2;return pos0=pos,pos1=pos,pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result0=input.charAt(pos),pos++):(result0=null,0===reportFailures&&matchFailed("[0-9]")),null!==result0?(/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1?result0=[result0,result1]:(result0=null,pos=pos2)):(result0=null,pos=pos2),null!==result0?(pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1?(/^[0-9]/.test(input.charAt(pos))?(result2=input.charAt(pos),pos++):(result2=null,0===reportFailures&&matchFailed("[0-9]")),null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,hour,minute){var today=new Date;return today.setHours(parseInt(hour.join(""),10)),today.setMinutes(parseInt(minute.join(""),10)),today.setSeconds(0),today.setMilliseconds(0),today}(pos0,result0[0],result0[1])),null===result0&&(pos=pos0),result0}function parse_time_hour(){var result0,pos0;return pos0=pos,result0=parse_int1_2digit(),null!==result0&&(result0=function(offset,hour){var today=new Date;return today.setHours(hour),today.setMinutes(0),today.setSeconds(0),today.setMilliseconds(0),today}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_time_hour_min(){var result0,result1,result2,result3,pos0,pos1,pos2;return pos0=pos,pos1=pos,pos2=pos,/^[012]/.test(input.charAt(pos))?(result0=input.charAt(pos),pos++):(result0=null,0===reportFailures&&matchFailed("[012]")),result0=null!==result0?result0:"",null!==result0?(/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1?result0=[result0,result1]:(result0=null,pos=pos2)):(result0=null,pos=pos2),null!==result0?(/^[:'h,.]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[:'h,.]")),null!==result1?(pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result2=input.charAt(pos),pos++):(result2=null,0===reportFailures&&matchFailed("[0-9]")),null!==result2?(/^[0-9]/.test(input.charAt(pos))?(result3=input.charAt(pos),pos++):(result3=null,0===reportFailures&&matchFailed("[0-9]")),null!==result3?result2=[result2,result3]:(result2=null,pos=pos2)):(result2=null,pos=pos2),null!==result2?result0=[result0,result1,result2]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,hour,minute){var today=new Date;return today.setHours(hour.join("")),today.setMinutes(minute.join("")),today.setSeconds(0),today.setMilliseconds(0),today}(pos0,result0[0],result0[2])),null===result0&&(pos=pos0),result0}function parse_am_pm(){var result0,pos0;return pos0=pos,"am"===input.substr(pos,2).toLowerCase()?(result0=input.substr(pos,2),pos+=2):(result0=null,0===reportFailures&&matchFailed('"AM"')),null!==result0&&(result0=function(offset){return 12}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"pm"===input.substr(pos,2).toLowerCase()?(result0=input.substr(pos,2),pos+=2):(result0=null,0===reportFailures&&matchFailed('"PM"')),null!==result0&&(result0=function(offset){return 0}(pos0)),null===result0&&(pos=pos0)),result0}function parse_timezone_name(){var result0;return"utc"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"UTC"')),result0}function parse_date(){var result0,result1,result2,result3,result4,result5,pos0,pos1,pos2,pos3;return pos0=pos,pos1=pos,result0=parse_month_name(),null!==result0?(result1=parse__(),null!==result1?(result2=parse_int1_2digit(),null!==result2?(pos2=pos,pos3=pos,result3=parse__(),result3=null!==result3?result3:"",null!==result3?(44===input.charCodeAt(pos)?(result4=",",pos++):(result4=null,0===reportFailures&&matchFailed('","')),null!==result4?result3=[result3,result4]:(result3=null,pos=pos3)):(result3=null,pos=pos3),result3=null!==result3?result3:"",null!==result3?(result4=parse__(),null!==result4?(result5=parse_year_number(),null!==result5?result3=[result3,result4,result5]:(result3=null,pos=pos2)):(result3=null,pos=pos2)):(result3=null,pos=pos2),result3=null!==result3?result3:"",null!==result3?result0=[result0,result1,result2,result3]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,month,day,tail){var now=null;return now="object"==typeof tail?new Date(tail[2],month,day):new Date((new Date).getFullYear(),month,day)}(pos0,result0[0],result0[2],result0[3])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,result0=parse_day_of_week(),null!==result0&&(result0=function(offset,day){var now=new Date;return now.setDate(now.getDate()+(day-now.getDay())),now}(pos0,result0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"today"===input.substr(pos,5).toLowerCase()?(result0=input.substr(pos,5),pos+=5):(result0=null,0===reportFailures&&matchFailed('"TODAY"')),null!==result0&&(result0=function(offset){var now=new Date;return now.setHours(0),now.setMinutes(0),now.setSeconds(0),now.setMilliseconds(0),now}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"tomorrow"===input.substr(pos,8).toLowerCase()?(result0=input.substr(pos,8),pos+=8):(result0=null,0===reportFailures&&matchFailed('"TOMORROW"')),null!==result0&&(result0=function(offset){var now=new Date;return now.setDate(now.getDate()+1),now}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"yesterday"===input.substr(pos,9).toLowerCase()?(result0=input.substr(pos,9),pos+=9):(result0=null,0===reportFailures&&matchFailed('"YESTERDAY"')),null!==result0&&(result0=function(offset){var now=new Date;return now.setDate(now.getDate()-1),now}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,result0=parse_year_number(),null!==result0?(/^[\-]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[\\-]")),null!==result1?(result2=parse_int1_2digit(),null!==result2?(/^[\-]/.test(input.charAt(pos))?(result3=input.charAt(pos),pos++):(result3=null,0===reportFailures&&matchFailed("[\\-]")),null!==result3?(result4=parse_int1_2digit(),null!==result4?result0=[result0,result1,result2,result3,result4]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,year,month,date){return new Date(year,month-1,date)}(pos0,result0[0],result0[2],result0[4])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,result0=parse_int1_2digit(),null!==result0?(/^[.]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[.]")),null!==result1?(result2=parse_int1_2digit(),null!==result2?(/^[.]/.test(input.charAt(pos))?(result3=input.charAt(pos),pos++):(result3=null,0===reportFailures&&matchFailed("[.]")),null!==result3?(result4=parse_year_number(),null!==result4?result0=[result0,result1,result2,result3,result4]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,date,month,year){return new Date(year,month-1,date)}(pos0,result0[0],result0[2],result0[4])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,result0=parse_int1_2digit(),null!==result0?(result1=parse__(),null!==result1?(result2=parse_month_name(),null!==result2?(pos2=pos,result3=parse__(),null!==result3?(result4=parse_year_number(),null!==result4?result3=[result3,result4]:(result3=null,pos=pos2)):(result3=null,pos=pos2),result3=null!==result3?result3:"",null!==result3?result0=[result0,result1,result2,result3]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,date,month,tail){var now=null;return now="object"==typeof tail?new Date(tail[1],month,date):new Date((new Date).getFullYear(),month,date)}(pos0,result0[0],result0[2],result0[3])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,result0=parse_int1_2digit(),null!==result0?(47===input.charCodeAt(pos)?(result1="/",pos++):(result1=null,0===reportFailures&&matchFailed('"/"')),null!==result1?(result2=parse_int1_2digit(),null!==result2?(47===input.charCodeAt(pos)?(result3="/",pos++):(result3=null,0===reportFailures&&matchFailed('"/"')),null!==result3?(result4=parse_year_number(),null!==result4?result0=[result0,result1,result2,result3,result4]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,month,date,year){return new Date(year,month-1,date)}(pos0,result0[0],result0[2],result0[4])),null===result0&&(pos=pos0),null===result0&&(result0=parse_concatenated_date(),null===result0&&(pos0=pos,pos1=pos,"next"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"NEXT"')),null!==result0?(pos2=pos,result1=parse__(),null!==result1?(result2=parse_integer(),null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),result1=null!==result1?result1:"",null!==result1?(result2=parse__(),null!==result2?(result3=parse_inc_dec_period(),null!==result3?result0=[result0,result1,result2,result3]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,c,amount){var now=new Date,count=1;return"object"==typeof c&&(count=c[1]),now.setFullYear(now.getFullYear()+amount.y*count),now.setMonth(now.getMonth()+amount.m*count),now.setDate(now.getDate()+amount.d*count),now.setHours(now.getHours()+amount.h*count),now.setMinutes(now.getMinutes()+amount.i*count),now}(pos0,result0[1],result0[3])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"next"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"NEXT"')),null!==result0?(result1=parse__(),null!==result1?(result2=parse_day_of_week(),null!==result2?result0=[result0,result1,result2]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,day){var now=new Date;return now.setDate(now.getDate()+(day-now.getDay())+7),now}(pos0,result0[2])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"last"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"LAST"')),null!==result0?(pos2=pos,result1=parse__(),null!==result1?(result2=parse_integer(),null!==result2?result1=[result1,result2]:(result1=null,pos=pos2)):(result1=null,pos=pos2),result1=null!==result1?result1:"",null!==result1?(result2=parse__(),null!==result2?(result3=parse_inc_dec_period(),null!==result3?result0=[result0,result1,result2,result3]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,c,amount){var now=new Date,count=1;return"object"==typeof c&&(count=c[1]),now.setFullYear(now.getFullYear()+amount.y*-count),now.setMonth(now.getMonth()+amount.m*-count),now.setDate(now.getDate()+amount.d*-count),now.setHours(now.getHours()+amount.h*-count),now.setMinutes(now.getMinutes()+amount.i*-count),now}(pos0,result0[1],result0[3])),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"last"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"LAST"')),null!==result0?(result1=parse__(),null!==result1?(result2=parse_day_of_week(),null!==result2?result0=[result0,result1,result2]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,day){var now=new Date;return now.getDay()===day&&now.setDate(now.getDate()-7),now.setDate(now.getDate()+(day-now.getDay())),now}(pos0,result0[2])),null===result0&&(pos=pos0)))))))))))))),result0}function parse_concatenated_date(){var result0,pos0;return pos0=pos,result0=parse_str5_8digit(),null!==result0&&(result0=function(offset,digits){var year=0,month=0,day=0;return 5===digits.length||6===digits.length?(year=parseInt(digits.slice(0,4),10),month=parseInt(digits.slice(4),10)-1):(7===digits.length||8===digits.length)&&(year=parseInt(digits.slice(0,4),10),month=parseInt(digits.slice(4,6),10)-1,day=parseInt(digits.slice(6),10)),new Date(year,month,day)}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_month_name(){var result0,result1,pos0,pos1;return pos0=pos,pos1=pos,"jan"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"JAN"')),null!==result0?("uary"===input.substr(pos,4).toLowerCase()?(result1=input.substr(pos,4),pos+=4):(result1=null,0===reportFailures&&matchFailed('"UARY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 0}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"feb"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"FEB"')),null!==result0?("ruary"===input.substr(pos,5).toLowerCase()?(result1=input.substr(pos,5),pos+=5):(result1=null,0===reportFailures&&matchFailed('"RUARY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 1}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"mar"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"MAR"')),null!==result0?("ch"===input.substr(pos,2).toLowerCase()?(result1=input.substr(pos,2),pos+=2):(result1=null,0===reportFailures&&matchFailed('"CH"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 2}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"apr"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"APR"')),null!==result0?("il"===input.substr(pos,2).toLowerCase()?(result1=input.substr(pos,2),pos+=2):(result1=null,0===reportFailures&&matchFailed('"IL"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 3}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,"may"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"MAY"')),null!==result0&&(result0=function(offset){return 4}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"jun"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"JUN"')),null!==result0?("e"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"E"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 5}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"jul"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"JUL"')),null!==result0?("y"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"Y"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 6}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"aug"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"AUG"')),null!==result0?("ust"===input.substr(pos,3).toLowerCase()?(result1=input.substr(pos,3),pos+=3):(result1=null,0===reportFailures&&matchFailed('"UST"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 7}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"sep"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"SEP"')),null!==result0?("tember"===input.substr(pos,6).toLowerCase()?(result1=input.substr(pos,6),pos+=6):(result1=null,0===reportFailures&&matchFailed('"TEMBER"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 8}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"oct"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"OCT"')),null!==result0?("ober"===input.substr(pos,4).toLowerCase()?(result1=input.substr(pos,4),pos+=4):(result1=null,0===reportFailures&&matchFailed('"OBER"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 9}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"nov"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"NOV"')),null!==result0?("ember"===input.substr(pos,5).toLowerCase()?(result1=input.substr(pos,5),pos+=5):(result1=null,0===reportFailures&&matchFailed('"EMBER"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 10}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"dec"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"DEC"')),null!==result0?("ember"===input.substr(pos,5).toLowerCase()?(result1=input.substr(pos,5),pos+=5):(result1=null,0===reportFailures&&matchFailed('"EMBER"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 11}(pos0)),null===result0&&(pos=pos0)))))))))))),result0}function parse_year_number(){var result0,pos0;return pos0=pos,result0=parse_int2_or_4digit(),null!==result0&&(result0=function(offset,year){return 100>year?year+2e3:year}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_day_of_week(){var result0,result1,pos0,pos1;return pos0=pos,pos1=pos,"sun"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"SUN"')),null!==result0?("day"===input.substr(pos,3).toLowerCase()?(result1=input.substr(pos,3),pos+=3):(result1=null,0===reportFailures&&matchFailed('"DAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 0}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"mon"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"MON"')),null!==result0?("day"===input.substr(pos,3).toLowerCase()?(result1=input.substr(pos,3),pos+=3):(result1=null,0===reportFailures&&matchFailed('"DAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 1}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"tue"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"TUE"')),null!==result0?("sday"===input.substr(pos,4).toLowerCase()?(result1=input.substr(pos,4),pos+=4):(result1=null,0===reportFailures&&matchFailed('"SDAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 2}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"wed"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"WED"')),null!==result0?("nesday"===input.substr(pos,6).toLowerCase()?(result1=input.substr(pos,6),pos+=6):(result1=null,0===reportFailures&&matchFailed('"NESDAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 3}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"thu"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"THU"')),null!==result0?("rsday"===input.substr(pos,5).toLowerCase()?(result1=input.substr(pos,5),pos+=5):(result1=null,0===reportFailures&&matchFailed('"RSDAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 4}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"fri"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"FRI"')),null!==result0?("day"===input.substr(pos,3).toLowerCase()?(result1=input.substr(pos,3),pos+=3):(result1=null,0===reportFailures&&matchFailed('"DAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 5}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"sat"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"SAT"')),null!==result0?("urday"===input.substr(pos,5).toLowerCase()?(result1=input.substr(pos,5),pos+=5):(result1=null,0===reportFailures&&matchFailed('"URDAY"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return 6}(pos0)),null===result0&&(pos=pos0))))))),result0}function parse_inc_or_dec(){var result0;return result0=parse_increment(),null===result0&&(result0=parse_decrement()),result0}function parse_increment(){var result0,result1,result2,result3,result4,pos0,pos1;return pos0=pos,pos1=pos,43===input.charCodeAt(pos)?(result0="+",pos++):(result0=null,0===reportFailures&&matchFailed('"+"')),null!==result0?(result1=parse__(),null!==result1?(result2=parse_integer(),null!==result2?(result3=parse__(),null!==result3?(result4=parse_inc_dec_period(),null!==result4?result0=[result0,result1,result2,result3,result4]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,count,amount){return{count:count,amount:amount}}(pos0,result0[2],result0[4])),null===result0&&(pos=pos0),result0}function parse_decrement(){var result0,result1,result2,result3,result4,pos0,pos1;return pos0=pos,pos1=pos,45===input.charCodeAt(pos)?(result0="-",pos++):(result0=null,0===reportFailures&&matchFailed('"-"')),null!==result0?(result1=parse__(),null!==result1?(result2=parse_integer(),null!==result2?(result3=parse__(),null!==result3?(result4=parse_inc_dec_period(),null!==result4?result0=[result0,result1,result2,result3,result4]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,count,amount){return{count:-1*count,amount:amount}}(pos0,result0[2],result0[4])),null===result0&&(pos=pos0),result0}function parse_inc_dec_period(){var result0,result1,pos0,pos1;return pos0=pos,pos1=pos,"minute"===input.substr(pos,6).toLowerCase()?(result0=input.substr(pos,6),pos+=6):(result0=null,0===reportFailures&&matchFailed('"MINUTE"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:0,m:0,d:0,h:0,i:1}}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"hour"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"HOUR"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:0,m:0,d:0,h:1,i:0}}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"day"===input.substr(pos,3).toLowerCase()?(result0=input.substr(pos,3),pos+=3):(result0=null,0===reportFailures&&matchFailed('"DAY"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:0,m:0,d:1,h:0,i:0}}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"week"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"WEEK"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:0,m:0,d:7,h:0,i:0}}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"month"===input.substr(pos,5).toLowerCase()?(result0=input.substr(pos,5), +pos+=5):(result0=null,0===reportFailures&&matchFailed('"MONTH"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:0,m:1,d:0,h:0,i:0}}(pos0)),null===result0&&(pos=pos0),null===result0&&(pos0=pos,pos1=pos,"year"===input.substr(pos,4).toLowerCase()?(result0=input.substr(pos,4),pos+=4):(result0=null,0===reportFailures&&matchFailed('"YEAR"')),null!==result0?("s"===input.substr(pos,1).toLowerCase()?(result1=input.substr(pos,1),pos++):(result1=null,0===reportFailures&&matchFailed('"S"')),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset){return{y:1,m:0,d:0,h:0,i:0}}(pos0)),null===result0&&(pos=pos0)))))),result0}function parse_int1_2digit(){var result0,result1,pos0,pos1;return pos0=pos,pos1=pos,/^[0-9]/.test(input.charAt(pos))?(result0=input.charAt(pos),pos++):(result0=null,0===reportFailures&&matchFailed("[0-9]")),null!==result0?(/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),result1=null!==result1?result1:"",null!==result1?result0=[result0,result1]:(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,digits){return parseInt(digits.join(""),10)}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_int2_or_4digit(){var result0,result1,result2,result3,pos0,pos1,pos2;return pos0=pos,pos1=pos,/^[0-9]/.test(input.charAt(pos))?(result0=input.charAt(pos),pos++):(result0=null,0===reportFailures&&matchFailed("[0-9]")),null!==result0?(/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1?(pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result2=input.charAt(pos),pos++):(result2=null,0===reportFailures&&matchFailed("[0-9]")),null!==result2?(/^[0-9]/.test(input.charAt(pos))?(result3=input.charAt(pos),pos++):(result3=null,0===reportFailures&&matchFailed("[0-9]")),null!==result3?result2=[result2,result3]:(result2=null,pos=pos2)):(result2=null,pos=pos2),result2=null!==result2?result2:"",null!==result2?result0=[result0,result1,result2]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,digits){var value=digits.slice(0,2).join("");return"object"==typeof digits[2]&&(value+=digits[2].join("")),parseInt(value,10)}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_str5_8digit(){var result0,result1,result2,result3,result4,result5,result6,result7,pos0,pos1,pos2;return pos0=pos,pos1=pos,/^[0-9]/.test(input.charAt(pos))?(result0=input.charAt(pos),pos++):(result0=null,0===reportFailures&&matchFailed("[0-9]")),null!==result0?(/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1?(/^[0-9]/.test(input.charAt(pos))?(result2=input.charAt(pos),pos++):(result2=null,0===reportFailures&&matchFailed("[0-9]")),null!==result2?(/^[0-9]/.test(input.charAt(pos))?(result3=input.charAt(pos),pos++):(result3=null,0===reportFailures&&matchFailed("[0-9]")),null!==result3?(/^[0-9]/.test(input.charAt(pos))?(result4=input.charAt(pos),pos++):(result4=null,0===reportFailures&&matchFailed("[0-9]")),null!==result4?(pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result5=input.charAt(pos),pos++):(result5=null,0===reportFailures&&matchFailed("[0-9]")),null!==result5?(/^[0-9]/.test(input.charAt(pos))?(result6=input.charAt(pos),pos++):(result6=null,0===reportFailures&&matchFailed("[0-9]")),null!==result6?(/^[0-9]/.test(input.charAt(pos))?(result7=input.charAt(pos),pos++):(result7=null,0===reportFailures&&matchFailed("[0-9]")),null!==result7?result5=[result5,result6,result7]:(result5=null,pos=pos2)):(result5=null,pos=pos2)):(result5=null,pos=pos2),null===result5&&(pos2=pos,/^[0-9]/.test(input.charAt(pos))?(result5=input.charAt(pos),pos++):(result5=null,0===reportFailures&&matchFailed("[0-9]")),null!==result5?(/^[0-9]/.test(input.charAt(pos))?(result6=input.charAt(pos),pos++):(result6=null,0===reportFailures&&matchFailed("[0-9]")),null!==result6?result5=[result5,result6]:(result5=null,pos=pos2)):(result5=null,pos=pos2),null===result5&&(/^[0-9]/.test(input.charAt(pos))?(result5=input.charAt(pos),pos++):(result5=null,0===reportFailures&&matchFailed("[0-9]")))),result5=null!==result5?result5:"",null!==result5?result0=[result0,result1,result2,result3,result4,result5]:(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1)):(result0=null,pos=pos1),null!==result0&&(result0=function(offset,digits){var value=digits.slice(0,5).join("");return value+="object"==typeof digits[5]?digits[5].join(""):digits[5]}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse_integer(){var result0,result1,pos0;if(pos0=pos,/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]")),null!==result1)for(result0=[];null!==result1;)result0.push(result1),/^[0-9]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[0-9]"));else result0=null;return null!==result0&&(result0=function(offset,digits){return parseInt(digits.join(""),10)}(pos0,result0)),null===result0&&(pos=pos0),result0}function parse__(){var result0,result1;for(result0=[],/^[\t\n\r _]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[\\t\\n\\r _]"));null!==result1;)result0.push(result1),/^[\t\n\r _]/.test(input.charAt(pos))?(result1=input.charAt(pos),pos++):(result1=null,0===reportFailures&&matchFailed("[\\t\\n\\r _]"));return result0}function cleanupExpected(expected){expected.sort();for(var lastExpected=null,cleanExpected=[],i=0;i { + // Make the $location service available in root scope + $rootScope.location = $location; + $rootScope.isLoading = () => $http.pendingRequests.length !== 0; + $rootScope.clearError = () => { $rootScope.error = null; }; + $rootScope.$on('queryChange', $rootScope.clearError); + $rootScope.$on('queryChange', PuppetDB.cancel); + $rootScope.$on('filterChange', PuppetDB.cancel); + $rootScope.changePage = (page) => { + $location.search('page', page); + $rootScope.$broadcast('pageChange', { page }); + }; +}); + +angular.module('app').factory('$exceptionHandler', ($injector, $log) => + (exception, cause) => { + $log.error(exception, cause); + if (!cause) { + const $rootScope = $injector.get('$rootScope'); + $rootScope.error = exception.message; + } + } +); + +angular.module('app').config(($routeProvider) => + $routeProvider.when('/dashboard', { + templateUrl: 'controllers/dashboard/dashboard.tpl.html', + controller: 'DashboardCtrl', + controllerAs: 'dashboard', + reloadOnSearch: false, + }) + .when('/nodes', { + templateUrl: 'controllers/nodelist/nodelist.tpl.html', + controller: 'NodeListCtrl', + controllerAs: 'nodeList', + reloadOnSearch: false, + }) + .when('/node/:node', { + templateUrl: 'controllers/nodedetail/nodedetail.tpl.html', + controller: 'NodeDetailCtrl', + controllerAs: 'nodeDetail', + reloadOnSearch: false, + }) + .when('/events', { + templateUrl: 'controllers/events/events.tpl.html', + controller: 'EventsCtrl', + controllerAs: 'events', + reloadOnSearch: false, + }) + .when('/facts', { + templateUrl: 'controllers/facts/facts.tpl.html', + controller: 'FactsCtrl', + controllerAs: 'facts', + reloadOnSearch: false, + }) + .otherwise({ redirectTo: '/dashboard' }) +); diff --git a/dist/app/config.js.example b/dist/app/config.js.example new file mode 100644 index 0000000..983fd4f --- /dev/null +++ b/dist/app/config.js.example @@ -0,0 +1,46 @@ +// List of PuppetDB servers, pairs of name, URL and $http config object +// The first one will be used as the default server +PUPPETDB_SERVERS = [ + ['production', '/api'], + ['testing', '/api'] +]; + +// A list of important facts that you want shown in the node detail view +NODE_FACTS = [ + 'operatingsystem', + 'operatingsystemrelease', + 'manufacturer', + 'productname', + 'processorcount', + 'memorytotal', + 'ipaddress' +]; + +// The amount of hours since the last check-in after which a node is considered +// unresponsive +UNRESPONSIVE_HOURS = 2; + +// Customisable dashboard panels +// type can be either 'primary', 'success', 'info', 'warning' or 'danger' +// Different types will be displayed with different colors +DASHBOARD_PANELS = [ + { + name: 'Unresponsive nodes', + type: 'danger', + query: '#node.report_timestamp < @"now - 2 hours"' + }, + { + name: 'Nodes in production env', + type: 'success', + query: '#node.catalog_environment = production' + }, + { + name: 'Nodes in non-production env', + type: 'warning', + query: '#node.catalog_environment != production' + } +]; + +// Google Analytics settings +GA_TRACKING_ID = 'UA-XXXXXXXX-YY'; +GA_DOMAIN = 'auto'; diff --git a/dist/app/controllers/dashboard/dashboard.js b/dist/app/controllers/dashboard/dashboard.js new file mode 100644 index 0000000..77e869d --- /dev/null +++ b/dist/app/controllers/dashboard/dashboard.js @@ -0,0 +1,78 @@ +/* global angular DASHBOARD_PANELS */ +angular.module('app').controller('DashboardCtrl', class { + constructor($scope, PuppetDB, $location) { + this.loadMetrics = this.loadMetrics.bind(this); + this.$scope = $scope; + this.PuppetDB = PuppetDB; + this.$location = $location; + this.$scope.$on('queryChange', this.loadMetrics); + this.major = this.minor = this.patch = null; + this.checkVersion(); + } + + loadMetrics() { + this.getBean('num-nodes', 'activeNodes'); + this.getBean('num-resources', 'resources'); + this.getBean('avg-resources-per-node', 'avgResources'); + this.getBean('pct-resource-dupes', 'resDuplication', 100); + + if (DASHBOARD_PANELS) { + this.$scope.panels = DASHBOARD_PANELS; + } else { + this.$scope.panels = []; + } + for (const panel of this.$scope.panels) { + panel.count = undefined; // reset if we switched server + this.getNodeCount(panel.query, (count) => { panel.count = String(count); }); + } + + this.$scope.panelWidth = Math.max(2, Math.floor(12 / this.$scope.panels.length)); + } + + getBean(name, scopeName, multiply = 1) { + this.$scope[scopeName] = undefined; + const bean = this.major > 3 ? + 'puppetlabs.puppetdb.population' : 'puppetlabs.puppetdb.query.population'; + const metric = this.major > 3 ? + `${bean}:name=${name}` : `${bean}:type=default,name=${name}`; + return this.PuppetDB.getBean(metric) + .success((data) => { + this.$scope[scopeName] = (angular.fromJson(data).Value * multiply) + .toLocaleString() + .replace(/^(.*\..).*/, '$1'); + }) + .error((data, status) => { + if (status !== 0) { + throw new Error(`Could not fetch metric ${name} from PuppetDB`); + } + }); + } + + getNodeCount(query, callback) { + this.PuppetDB.query( + 'nodes', + ['extract', [['function', 'count']], this.PuppetDB.parse(query)], + {}, + (data) => callback(data[0].count) + ); + } + + setQuery(query) { + this.$location.search('query', query); + return this.$location.path('/nodes'); + } + + checkVersion() { + return this.PuppetDB.getVersion() + .success(data => { + this.major = parseInt(data.version.split('.')[0], 10); + this.minor = parseInt(data.version.split('.')[1], 10); + this.patch = parseInt(data.version.split('.')[2], 10); + if (this.major < 4 || (this.major === 3 && this.minor < 2)) { + throw new Error('This version of Puppet Explorer requires PuppetDB version 3.2.0+' + + `, you are running PuppetDB ${data.version}`); + } + return this.loadMetrics(); + }); + } +}); diff --git a/dist/app/controllers/dashboard/dashboard.tpl.html b/dist/app/controllers/dashboard/dashboard.tpl.html new file mode 100644 index 0000000..71fbb56 --- /dev/null +++ b/dist/app/controllers/dashboard/dashboard.tpl.html @@ -0,0 +1,114 @@ +
    +
    +
    +

    Nodes

    +
    {{activeNodes}}
    +
    +
    +
    +
    +

    Resources

    +
    {{resources}}
    +
    +
    +
    +
    +

    Avg resources/node

    +
    {{avgResources}}
    +
    +
    +
    +
    +

    Resource duplication %

    +
    {{resDuplication}}
    +
    +
    +
    + +
    +
    +
    +

    {{panel.name}}

    +
    {{panel.count}}
    +
    +
    +
    + +
    +
    +

    Syntax

    +

    Use fact=value to search for nodes where fact equals value. + To search for structured facts use dots between each part of the fact path, for example + foo.bar=baz.

    + +

    Resources can be matched using the syntax type[title]{param=value}. + The part in brackets is optional. You can also specify ~ before the title + to do a regexp match on the title. Type names and class names are case insensitive. + A resource can be preceded by @@ to match exported resources, the default is to only + match "local" resources.

    + +

    Strings can contain letters, numbers or the characters :-_ without needing to be quoted. + If they contain any other characters they need to be quoted with single or double quotes. + Use backslash (\) to escape quotes within a quoted string or double backslash for backslashes.

    + +

    An unquoted number or the strings true/false will be interpreted as numbers and boolean + values, use quotation marks around them to search for them as strings instead.

    + +

    A @ sign before a string causes it to be interpreted as a date parsed with + timespec. + For example @"now - 2 hours".

    + +

    A # sign can be used to do a subquery, against the nodes endpoint for example to + query the report_timestamp, catalog_timestamp or facts_timestamp + fields. For example #node.report_timestamp < @"now - 2 hours".

    + +

    A subquery using the # sign can have a block of expressions instead of a single + expression. For example #node { report_timestamp > @"now - 4 hours" and + report_timestamp < @"now - 2 hours" }

    + +

    A bare string without comparison operator will be treated as a regexp match against the certname.

    +
    + +
    +

    Comparison operators

    + + + + + + + + + + +
    =Equality
    !=Not equal
    ~Regexp match
    !~Not regexp match
    <Less than
    <=Less than or equal
    >Greater than
    >=Greater than or equal
    + +

    Logical operators

    + + + + +
    not(unary op)
    and
    or
    +

    Shown in precedence order from highest to lowest. Use parenthesis to change order in an expression.

    +
    + +
    +

    Query Examples

    + Nodes with package mysql-server and amd64 arcitecture +
    + package["mysql-server"] and architecture=amd64 +
    + Nodes with the class Postgresql::Server and a version set to 9.3 +
    + class[postgresql::server]{ version="9.3" } +
    + Nodes with 4 or 8 processors running Linux +
    + (processorcount=4 or processorcount=8) and kernel=Linux +
    + Nodes that haven't reported in the last 2 hours +
    + #node.report_timestamp<@"now - 2 hours" +
    +
    +
    diff --git a/dist/app/controllers/events/events.js b/dist/app/controllers/events/events.js new file mode 100644 index 0000000..bee3700 --- /dev/null +++ b/dist/app/controllers/events/events.js @@ -0,0 +1,299 @@ +/* global angular DASHBOARD_PANELS */ +import moment from 'moment'; +angular.module('app').controller('EventsCtrl', class { + constructor($scope, $rootScope, $location, PuppetDB) { + this.setFields = this.setFields.bind(this); + this.reset = this.reset.bind(this); + this.fetchEvents = this.fetchEvents.bind(this); + this.fetchContainingClasses = this.fetchContainingClasses.bind(this); + this.fetchResourceCounts = this.fetchResourceCounts.bind(this); + this.fetchStatusCounts = this.fetchStatusCounts.bind(this); + this.drawChart = this.drawChart.bind(this); + this.$scope = $scope; + this.$rootScope = $rootScope; + this.$location = $location; + this.PuppetDB = PuppetDB; + this.$scope.$on('queryChange', this.reset); + this.$scope.$on('pageChange', this.fetchEvents); + this.$scope.$on('$locationChangeSuccess', this.setFields); + this.$scope.$on('filterChange', this.reset); + this.$scope.perPage = 50; + this.mode = {}; + this.setFields(); + } + + // Sets fields values to match the URL parameters + setFields(event, exclude) { + this.mode.current = this.$location.search().mode || 'latest'; + this.mode[this.mode.current] = true; + this.$scope.reportHash = this.$location.search().report; + this.$scope.dateFrom = this.$location.search().date_from || moment.utc().format('YYYY-MM-DD'); + this.$scope.dateTo = this.$location.search().date_to || moment.utc().format('YYYY-MM-DD'); + return this.reset(event, exclude); + } + + reset(event, exclude) { + if (event && event.name === 'pageChange') { + this.setFields(); + } + this.$location.search('page', null); + this.$scope.numItems = undefined; + this.fetchEvents(); + if (exclude !== 'containing_class') { this.fetchContainingClasses(); } + if (exclude !== 'resource_type') { this.fetchResourceCounts(); } + if (exclude !== 'status') { return this.fetchStatusCounts(); } + return event; + } + + // Switch mode to 'latest' or 'report' + // either vieeing latest report of a specified report + setMode(mode) { + this.mode.current = mode; + this.$location.search('mode', mode); + return this.setFilters(); + } + + // Public: Set URL to match currently selected filters + setFilters() { + this.$location.search('report', this.$scope.reportHash); + this.$location.search('date_from', moment(this.$scope.dateFrom).format('YYYY-MM-DD')); + this.$location.search('date_to', moment(this.$scope.dateTo).format('YYYY-MM-DD')); + return this.$rootScope.$broadcast('filterChange'); + } + + // Public: Create a event query for current filters + // + // exclude - A {String} filter to exclude from the generated query + // + // Returns: A {Array} PuppetDB query + createEventQuery(exclude = false) { + const query = ['and']; + if (this.mode.current === 'latest') { + query.push(['=', 'latest_report?', true]); + } else if (this.mode.current === 'report') { + query.push(['=', 'report', this.$scope.reportHash]); + } else { + query.push(['>', 'timestamp', moment.utc(this.$scope.dateFrom).toISOString()]); + query.push(['<', 'timestamp', moment.utc(this.$scope.dateTo).add(1, 'days').toISOString()]); + } + + if ((this.$location.search().containing_class != null) && exclude !== 'containing_class') { + let cc = this.$location.search().containing_class; + if (cc === 'none') { + cc = null; + } + query.push(['=', 'containing_class', cc]); + } + if ((this.$location.search().resource_type != null) && exclude !== 'resource_type') { + query.push(['=', 'resource_type', this.$location.search().resource_type]); + } + if ((this.$location.search().status != null) && exclude !== 'status') { + query.push(['=', 'status', this.$location.search().status.toLowerCase()]); + } + + return query; + } + + // Public: Fetch the list of events for the currect page + // + // Returns: `undefined` + fetchEvents() { + this.events = undefined; + + this.PuppetDB.parseAndQuery('events', + this.$location.search().query, + this.createEventQuery(), + { + offset: this.$scope.perPage * ((this.$location.search().page || 1) - 1), + limit: this.$scope.perPage, + order_by: angular.toJson([{ field: 'timestamp', order: 'desc' }]), + }, + (data, total) => { + this.$scope.numItems = total; + this.events = data; + } + ); + } + + // Public: Toggle the display of a row in the event table + // + // event - The {Object} for the event + // + // Returns: `undefined` + toggleRow(event) { + event.show = !event.show; + } + + fetchContainingClasses() { + this.drawChart('containingChart', 'Containing class'); + this.PuppetDB.parseAndQuery('event-counts', + this.$location.search().query, + this.createEventQuery('containing_class'), + { + summarize_by: 'containing_class', + }, + (data) => { + const chartData = []; + for (const item of data) { + const key = item.subject.title || 'none'; + const value = item.failures + item.successes + item.noops + item.skips; + chartData.push([key, value]); + } + this.drawChart('containingChart', 'Containing class', chartData); + } + ); + } + + fetchResourceCounts() { + this.drawChart('resourceChart', 'Resource'); + this.PuppetDB.query('events', + ['extract', [['function', 'count'], 'resource_type'], + this.PuppetDB.combine( + this.PuppetDB.parse(this.$location.search().query), + this.createEventQuery('resource_type') + ), + ['group_by', 'resource_type']], + null, + (data) => { + const chartData = data.map(i => [i.resource_type || 'none', i.count]); + this.drawChart('resourceChart', 'Resource type', chartData); + } + ); + } + + fetchStatusCounts() { + this.drawChart('statusChart', 'Event status'); + this.PuppetDB.query('events', + ['extract', [['function', 'count'], 'status'], + this.PuppetDB.combine( + this.PuppetDB.parse(this.$location.search().query), + this.createEventQuery('status') + ), + ['group_by', 'status']], + null, + (data) => { + const dataHash = {}; + for (const item of data) { + dataHash[item.status] = item.count; + } + const chartData = [ + ['Success', dataHash.success || 0], + ['Skipped', dataHash.skipped || 0], + ['Failure', dataHash.failure || 0], + ['Noop', dataHash.noop || 0], + ]; + this.drawChart('statusChart', 'Event status', chartData); + } + ); + } + + drawChart(name, title, data) { + let colors; + let sliceText; + let enableInteractivity; + if (data) { + colors = [ + '#A1CF64', // green + '#FFCC66', // yellow + '#D95C5C', // red + '#6ECFF5', // blue + '#564F8A', // purple + '#00B5AD', // teal + '#F05940', // orange + ]; + sliceText = 'percent'; + enableInteractivity = true; + } else { + colors = ['#BBBBBB']; + sliceText = 'label'; + enableInteractivity = false; + } + this.$scope[name] = { + type: 'PieChart', + options: { + backgroundColor: 'transparent', + colors, + pieSliceText: sliceText, + enableInteractivity, + height: 250, + chartArea: { + width: '100%', + height: '85%', + }, + }, + data: [['Type', 'Number']].concat(data || [['Loading', 1]]), + }; + // Always trigger a redraw of charts + this.$rootScope.$broadcast('resizeMsg'); + } + + // Public: Return the CSS class event states should correspond to + // + // status - The {String} event status + // + // Returns: A {String} with the CSS class + color(status) { + switch (status) { + case 'success': + return 'success'; + case 'noop': + return 'text-muted'; + case 'failure': + return 'danger'; + case 'skipped': + return 'warning'; + default: + return ''; + } + } + + // Public: Set the current chart selection on click + // + // param - The {String} name of the url parameter + // data - The {Object} data values in the chart + // item - The {Object} selected item + // + // Returns: `undefined` + onChartSelect(param, data, item) { + if (item) { + this.$location.search(param, data[item.row + 1][0]); + } else { + this.$location.search(param, null); + } + this.$rootScope.$broadcast('filterChange', param); + } + + // Public: Set the chart selection when the chart is loaded + // + // param - The {String} name of the url parameter + // data - The {Object} data values in the chart + // chart - The {Object} ChartWrapper object + // + // Returns: `undefined` + setChartSelection(param, data, chart) { + if (!chart.getOptions().enableInteractivity) { return; } + const selected = this.$location.search()[param]; + if (selected != null) { + let row = null; + for (let i = 0; i < data.length; i++) { + const r = data[i]; + if (r[0] === selected) { row = i; } + } + if (row) { + chart.getChart().setSelection([{ row: row - 1 }]); + } + } + } + + toggleDatePicker($event, name) { + $event.preventDefault(); + $event.stopPropagation(); + this.$scope[name] = !this.$scope[name]; + } + + // Switch to events view for a specified report + selectReport(report) { + this.$scope.reportHash = report; + this.setMode('report'); + } +}); diff --git a/dist/app/controllers/events/events.tpl.html b/dist/app/controllers/events/events.tpl.html new file mode 100644 index 0000000..b6850e7 --- /dev/null +++ b/dist/app/controllers/events/events.tpl.html @@ -0,0 +1,129 @@ + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    +
    + +
    + + + + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    +

    Containing class

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Resource type

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Event status

    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NodeTypeTitlePropertyFromTo
    {{event.certname}}{{event.resource_type}}{{event.resource_title}}{{event.property}}{{event.old_value}}{{event.new_value}}
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Message{{event["message"]}}
    Containing-class{{event.containing_class}}
    Containment-path + +
    File{{event["file"]}}:{{event["line"]}}
    Timestamp
    Report{{event.report}}
    +
    + + diff --git a/dist/app/controllers/facts/facts.js b/dist/app/controllers/facts/facts.js new file mode 100644 index 0000000..c2ba1d7 --- /dev/null +++ b/dist/app/controllers/facts/facts.js @@ -0,0 +1,214 @@ +/* global angular */ +angular.module('app').controller('FactsCtrl', class { + constructor($scope, $rootScope, $location, PuppetDB) { + this.syncCharts = this.syncCharts.bind(this); + this.reloadCharts = this.reloadCharts.bind(this); + this.$scope = $scope; + this.$rootScope = $rootScope; + this.$location = $location; + this.PuppetDB = PuppetDB; + this.charts = new Map(); + this.getFactNames(); + this.$scope.$on('queryChange', this.reloadCharts); + this.$scope.$on('$locationChangeSuccess', this.syncCharts); + this.syncCharts(); + } + + // Turn a fact path into a string + // + // fact - The {Array} fact path + factPathToString(path) { + return path.map((pathComponent) => { + if (typeof(pathComponent) === 'number') { + return String(pathComponent); + } + if (/^[.\d]+$/.test(pathComponent)) { + return `\"${pathComponent}\"`; + } + return pathComponent; + }).join('.'); + } + + // Turn a fact path string into a path array + // + // This is somewhat naive and assumes that facts don't contain + // certain characters + factPathFromString(name) { + return name.split('.').map((pathComponent) => { + if (/^\d+$/.test(pathComponent)) { + return parseInt(pathComponent, 10); + } else if (pathComponent[0] === '"') { + return pathComponent.slice(1, -1); + } + return pathComponent; + }); + } + + // Public: Get the list of currently active charts + // + // Returns: An {Array} of active charts + chartList() { + if (this.$location.search().facts) { + return this.$location.search().facts.split(','); + } + return []; + } + + // Check if a chart is activated or not + // + // Returns: A {Boolean} indicating if the chart is active + chartActive(chart) { + return this.chartList().indexOf(this.factPathToString(chart)) !== -1; + } + + // Public: Sync the charts object with the list of charts to display + // + // Returns: `undefined` + syncCharts() { + const chartList = this.chartList(); + for (const chart of Object.keys(this.charts)) { + if (chartList.includes(chart)) { delete this.charts[chart]; } + } + for (const chart of chartList) { + if (!Object.keys(this.charts).includes(chart)) { + this.addChart(this.factPathFromString(chart)); + } + } + } + + // Public: Reload all charts + // + // Returns: `undefined` + reloadCharts() { + for (const [, chart] of this.charts) { + this.addChart(chart.factPath); + } + } + + // Public: Fetch the names of available facts + // + // Returns: `undefined` + getFactNames() { + this.PuppetDB.query('fact-paths', + null, + { order_by: angular.toJson([{ field: 'path', order: 'asc' }]) }, + data => { + this.factPaths = angular.fromJson(data).filter(fact => fact.path[0][0] !== '_') + .map(fact => fact.path); + } + ); + } + + // Public: Toggle display of a chart + toggleChart(fact) { + if (this.chartActive(fact)) { + this.deleteChart(this.factPathToString(fact)); + } else { + this.addChart(fact); + } + } + + // Public: Add or reload a chart + // + // fact - The {Array} fact path to add + // + // Returns: `undefined` + addChart(fact) { + const facts = this.chartList(); + if (!facts.includes(this.factPathToString(fact))) { + facts.push(this.factPathToString(fact)); + this.$location.search('facts', facts.join(',')); + } + this.charts[this.factPathToString(fact)] = { + factPath: fact, + type: 'PieChart', + options: { + backgroundColor: 'transparent', + width: 450, + height: 300, + chartArea: { + width: '100%', + height: '100%', + left: 10, + top: 20, + }, + colors: ['#BBBBBB'], + pieSliceText: 'label', + enableInteractivity: false, + }, + data: [['Value', 'Number'], ['Loading', 1]], + }; + + return this.PuppetDB.parseAndQuery('fact-contents', + this.$location.search().query, + ['=', 'path', fact], + {}, + (data) => { this.setChartData(fact, data); } + ); + } + + // Public: Remove a chart + // + // fact - The {String} path of the fact to remove + // + // Returns: `undefined` + deleteChart(fact) { + const facts = this.chartList(); + facts.splice(facts.indexOf(fact, 1)); + this.$location.search('facts', facts.join(',') || null); + return delete this.charts[fact]; + } + + // Public: Set the data to be drawn on a chart + // + // fact - The {Array} path of the fact to chart + // data - The {Array} fact values + // + // Returns: `undefined` + setChartData(fact, data) { + const factString = this.factPathToString(fact); + this.charts[factString].options.colors = [ + '#A1CF64', // green + '#FFCC66', // yellow + '#D95C5C', // red + '#6ECFF5', // blue + '#564F8A', // purple + '#00B5AD', // teal + '#F05940', // orange + ]; + this.charts[factString].options.pieSliceText = 'percent'; + this.charts[factString].options.enableInteractivity = true; + this.charts[factString].data = [['Value', 'Number']]; + + // Count occurrences of each value + const values = new Map(); + for (const f of data) { + let quotedVal; + if (typeof(f.value) === 'string') { + quotedVal = `\"${f.value.replace('"', '\\"')}\"`; + } else { + quotedVal = `${f.value}`; + } + values[quotedVal] = values[quotedVal] + 1 || 1; + } + + // Push the values as rows + for (const k of values) { + const v = values[k]; + this.charts[factString].data.push([k, v]); + } + + // Always trigger a redraw of charts + return this.$rootScope.$broadcast('resizeMsg'); + } + + chartSelect(fact, item) { + const value = this.charts[fact].data[item.row + 1][0]; + const { query } = this.$location.search(); + if (query) { + this.$location.search('query', `(${query}) and ${fact}=${value}`); + } else { + this.$location.search('query', `${fact}=${value}`); + } + } +}); diff --git a/dist/app/controllers/facts/facts.tpl.html b/dist/app/controllers/facts/facts.tpl.html new file mode 100644 index 0000000..e07431d --- /dev/null +++ b/dist/app/controllers/facts/facts.tpl.html @@ -0,0 +1,31 @@ +
    +
    +
    + + +
    + + + + + + + +
    + {{factCompontent}} / +
    +
    + +
    +
    +
    + +

    {{chart}}

    +
    +
    +
    +
    +
    +
    +
    diff --git a/dist/app/controllers/menu/menu.js b/dist/app/controllers/menu/menu.js new file mode 100644 index 0000000..5292056 --- /dev/null +++ b/dist/app/controllers/menu/menu.js @@ -0,0 +1,30 @@ +/* global angular */ +angular.module('app').controller('MenuCtrl', class { + constructor($scope, $rootScope, $location, PuppetDB) { + this.$scope = $scope; + this.$rootScope = $rootScope; + this.$location = $location; + this.PuppetDB = PuppetDB; + this.$scope.server = this.PuppetDB.server(); + this.servers = this.PuppetDB.servers; + } + + currentView() { + return this.$location.path().split('/')[1]; + } + + // FIXME: Both getter and setter + view(path) { + if (path) { + this.$location.path(path); + this.$location.search('page', null); // Clear page when switching + } + return this.$location.path().split('/')[1]; + } + + setServer(server) { + this.PuppetDB.server(server); + // Not technically, but we have to do the same things + this.$rootScope.$broadcast('queryChange'); + } +}); diff --git a/dist/app/controllers/nodedetail/nodedetail.js b/dist/app/controllers/nodedetail/nodedetail.js new file mode 100644 index 0000000..82322e4 --- /dev/null +++ b/dist/app/controllers/nodedetail/nodedetail.js @@ -0,0 +1,118 @@ +/* global angular NODE_FACTS */ +angular.module('app').controller('NodeDetailCtrl', class { + constructor($location, $routeParams, $scope, PuppetDB) { + this.reset = this.reset.bind(this); + this.fetchReports = this.fetchReports.bind(this); + this.fetchFacts = this.fetchFacts.bind(this); + this.$location = $location; + this.$routeParams = $routeParams; + this.$scope = $scope; + this.PuppetDB = PuppetDB; + this.node = this.$routeParams.node; + // Reload nodes if either the page changes + this.$scope.$on('pageChange', this.fetchReports); + this.$scope.perPage = 10; + this.reset(); + } + + reset() { + this.reports = undefined; + this.$location.search('page', null); + this.$scope.numItems = undefined; + this.fetchReports(); + return this.fetchFacts(); + } + + // Public: Fetches reports for node and stores them in @reports + fetchReports() { + this.reports = this.PuppetDB.parseAndQuery( + 'reports', + this.$location.search().query, + ['=', 'certname', this.node], + { + order_by: angular.toJson([{ field: 'end_time', order: 'desc' }]), + offset: this.$scope.perPage * ((this.$location.search().page || 1) - 1), + limit: this.$scope.perPage, + }, + (data, total) => { + this.reports = data; + this.$scope.numItems = total; + for (const report of this.reports) { + this.PuppetDB.parseAndQuery( + 'event-counts', + null, + ['=', 'report', report.hash], + { + summarize_by: 'certname', + }, + (d) => { report.events = d[0]; } + ); + } + } + ); + } + + // Public: Fetch facts and store them in @facts + // + // Returns: `undefined` + fetchFacts() { + return this.PuppetDB.parseAndQuery( + 'fact-contents', + null, + ['=', 'certname', this.node], + { order_by: angular.toJson([{ field: 'name', order: 'asc' }]) }, + (data) => { + this.facts = data.filter(fact => fact.name[0] !== '_').map((fact) => { + // insert some spaces to break lines + fact.value = String(fact.value).replace(/(.{25})/g, '\u200B$1'); + return fact; + }); + } + ); + } + + // Public: Get the list of important facts to show in detail view + // + // Returns: A {Array} of important facts {String}. + importantFacts() { + return this.facts.filter((fact) => NODE_FACTS.indexOf(fact.name) !== -1) + .sort((a, b) => NODE_FACTS.indexOf(a.name) - NODE_FACTS.indexOf(b.name)); + } + + // Public: Return the status of a node + // + // node - The {Object} node + // + // Returns: The {String} "failure", "skipped", "noop", "success" or "none" + // of `null` if no status known. + // + // TODO: This is largely duplicated from nodelist, but can be simplified + // significantly when the PuppetDB 3.0 v4 API is released and moved to + // a common file (PuppetDB service likely). + status(report) { + if (report === undefined) { return 'glyphicon-refresh spin'; } + if (report === null) { return 'glyphicon-question-sign'; } + if (report.status === 'failed') { return 'glyphicon-warning-sign text-danger'; } + if (report.status === 'changed') { return 'glyphicon-exclamation-sign text-success'; } + if (report.events && report.events.failures > 0) { + return 'glyphicon-warning-sign text-danger'; + } + if (report.events && report.events.skips > 0) { + return 'glyphicon-exclamation-sign text-warning'; + } + if (report.events && report.events.noops > 0) { + return 'glyphicon-exclamation-sign text-info'; + } + if (report.events && report.events.successes > 0) { + return 'glyphicon-exclamation-sign text-success'; + } + return 'glyphicon-ok-sign text-muted'; + } + + // Switch to events view for a specified report + selectReport(report) { + this.$location.search('mode', 'report'); + this.$location.search('report', report); + return this.$location.path('/events'); + } +}); diff --git a/dist/app/controllers/nodedetail/nodedetail.tpl.html b/dist/app/controllers/nodedetail/nodedetail.tpl.html new file mode 100644 index 0000000..f5dd405 --- /dev/null +++ b/dist/app/controllers/nodedetail/nodedetail.tpl.html @@ -0,0 +1,71 @@ + +
    +
    + +
    +
    Reports
    + + + + + + + + + + + + + + + + + + + +
    TimeSuccessesNoopsSkipsFailures
    + + {{report.events.successes || ""}}{{report.events.noops || ""}}{{report.events.skips || ""}}{{report.events.failures || ""}} + +
    + +
    +
    +
    +
    +
    Facts
    + + + + + + + + + + + +
    FactValue
    {{factCompontent}} / {{fact.value}}
    +
    + +
    +
    All facts
    + + + + + + + + + + + +
    FactValue
    {{factCompontent}} / {{fact.value}}
    +
    +
    +
    diff --git a/dist/app/controllers/nodelist/nodelist.js b/dist/app/controllers/nodelist/nodelist.js new file mode 100644 index 0000000..1d2eba2 --- /dev/null +++ b/dist/app/controllers/nodelist/nodelist.js @@ -0,0 +1,113 @@ +/* global angular UNRESPONSIVE_HOURS moment */ +angular.module('app').controller('NodeListCtrl', class { + constructor($location, $scope, PuppetDB) { + // Reload nodes if either the page changes + this.reset = this.reset.bind(this); + this.fetchNodes = this.fetchNodes.bind(this); + this.fetchNodeEventCount = this.fetchNodeEventCount.bind(this); + this.selectNode = this.selectNode.bind(this); + this.$location = $location; + this.$scope = $scope; + this.PuppetDB = PuppetDB; + this.$scope.$on('pageChange', this.fetchNodes); + // Reset pagination and reload nodes if query changes + this.$scope.$on('queryChange', this.reset); + this.$scope.perPage = 50; + this.reset(); + } + + reset() { + this.$location.search('page', null); + this.$scope.numItems = undefined; + return this.fetchNodes(); + } + + // Public: Fetch the list of nodes for the current query + // + // Returns: `undefined` + fetchNodes() { + this.nodes = undefined; + return this.PuppetDB.parseAndQuery( + 'nodes', + this.$location.search().query, + null, + { + offset: this.$scope.perPage * ((this.$location.search().page || 1) - 1), + limit: this.$scope.perPage, + order_by: angular.toJson([{ field: 'certname', order: 'asc' }]), + }, + (data, total) => { + this.$scope.numItems = total; + this.nodes = data; + for (const node of this.nodes) { + this.fetchNodeEventCount(node); + } + if (this.$location.search().node != null) { + this.fetchSelectedNode(); + } + } + ); + } + + // Public: Fetch node event counts + // + // node - The {Object} node to fetch event counts for + // + // Returns: `undefined` + fetchNodeEventCount(node) { + if (!node.latest_report_hash) { return; } + const resp = this.PuppetDB.getQuery(`reports/${node.latest_report_hash}/metrics`); + this.PuppetDB.handleResponse(resp, ((n) => + (data) => { + node.metrics = {}; + // Create a nested hash out of all the metrics + for (const metric of data) { + if (n.metrics[metric.category] == null) { n.metrics[metric.category] = {}; } + node.metrics[metric.category][metric.name] = metric.value; + } + } + )(node) + ); + } + + // Public: Select a node to show info for + // + // node - The node {Object} + // + // Returns: `undefined` + selectNode(node) { + return this.$location.path(`/node/${node.certname}`); + } + + // Public: set the query to find a node and show events for it + // + // node - The {String} name of the node + // + // Returns: `undefined` + showEvents(node) { + this.$location.search('query', `\"${node}\"`); + return this.$location.path('/events'); + } + + // Public: Return the status of a node + // + // node - The {Object} node + // + // Returns: The {String} "failure", "skipped", "noop", "success" or "none" + // of `null` if no status known. + nodeStatus(node) { + switch (node.latest_report_status) { + case 'failed': return 'glyphicon-warning-sign text-danger'; + case 'changed': return 'glyphicon-exclamation-sign text-success'; + case 'unchanged': return 'glyphicon-exclamation-sign text-success'; + default: return 'glyphicon-question-sign'; + } + } + + // Return if a node is unresponsive or not + nodeUnresponsive(node) { + if (node.report_timestamp == null) { return true; } + const hours = UNRESPONSIVE_HOURS || 2; + return moment(node.report_timestamp).isBefore(moment.utc().subtract(hours, 'hours')); + } +}); diff --git a/dist/app/controllers/nodelist/nodelist.tpl.html b/dist/app/controllers/nodelist/nodelist.tpl.html new file mode 100644 index 0000000..d7c77a0 --- /dev/null +++ b/dist/app/controllers/nodelist/nodelist.tpl.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    Last runSuccessesNoopsSkipsFailures
    {{node.certname}} + + + {{node.metrics.events.success || ""}}{{node.metrics.events.noop || ""}}{{node.metrics.events.skip || ""}}{{node.metrics.events.failure || ""}} + +
    + + diff --git a/dist/app/controllers/search/search.js b/dist/app/controllers/search/search.js new file mode 100644 index 0000000..255ac20 --- /dev/null +++ b/dist/app/controllers/search/search.js @@ -0,0 +1,32 @@ +/* global angular */ +angular.module('app').controller('SearchCtrl', class { + constructor($scope, $rootScope, $location) { + this.$scope = $scope; + this.$rootScope = $rootScope; + this.$location = $location; + this.$scope.query = this.$location.search().query; + this.$scope.$on('$locationChangeSuccess', () => { + const old = this.$scope.query; + this.$scope.query = this.$location.search().query; + if (old !== this.$scope.query) { + this.$rootScope.$broadcast('queryChange', { query: this.$scope.query }); + } + }); + // Reset filters on query change + this.$rootScope.$on('queryChange', () => { + this.$location.search('containing_class', null); + this.$location.search('resource_type', null); + this.$location.search('status', null); + return this.$location.search('node', null); + }); + } + + submit() { + this.$location.search('query', this.$scope.query); + + this.$rootScope.$broadcast('queryChange', { query: this.$scope.query }); + + // Change view to nodes if we are on dashboard + if (this.$location.path() === '/dashboard') { this.$location.path('/nodes'); } + } +}); diff --git a/dist/app/css/style.css b/dist/app/css/style.css new file mode 100644 index 0000000..7484f62 --- /dev/null +++ b/dist/app/css/style.css @@ -0,0 +1,44 @@ +.table-hover, .nav, .pagination, .carousel, .panel-title a, [ng-click] { cursor: pointer; } + +.reveal-animation.ng-enter { + -webkit-animation: enter_sequence 0.4s linear; /* Safari/Chrome */ + animation: enter_sequence 0.4s linear; /* IE10+ and Future Browsers */ +} +@-webkit-keyframes enter_sequence { + from { opacity:0; } + to { opacity:1; } +} +@keyframes enter_sequence { + from { opacity:0; } + to { opacity:1; } +} + +.reveal-animation.ng-leave { + -webkit-animation: leave_sequence 0.2s linear; /* Safari/Chrome */ + animation: leave_sequence 0.2s linear; /* IE10+ and Future Browsers */ +} +@-webkit-keyframes leave_sequence { + from { opacity:1; } + to { opacity:0; } +} +@keyframes leave_sequence { + from { opacity:1; } + to { opacity:0; } +} + +.spin{ + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + -webkit-animation: spin 1s infinite linear; + -moz-animation: spin 1s infinite linear; + -o-animation: spin 1s infinite linear; + animation: spin 1s infinite linear; +} +@-webkit-keyframes spin { + from {-webkit-transform: rotate(0deg);} + to {-webkit-transform: rotate(359deg);} +} +@keyframes spin { + from {transform: rotate(0deg);} + to {transform: rotate(359deg);} +} diff --git a/dist/app/favicon.ico b/dist/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..85b071ac3c6e816a5c6efab514f5adc5fcdf2f20 GIT binary patch literal 1150 zcmajbODIHf6vy%75nR#e(aanOz@h z0tf75!-EFfO&UdQ;gKN_GpBDKM%%TY)KLhJvJcxKtMc!N@h9TmM f7WwpwypM_8E{L3SR&MdUEAo;|c7^OS%0<2aCoBf| literal 0 HcmV?d00001 diff --git a/dist/app/fonts/glyphicons-halflings-regular.eot b/dist/app/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..4a4ca865d67e86f961bc6e2ef00bffa4e34bb9ed GIT binary patch literal 20335 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

    !oRptb@n+ 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 literal 0 HcmV?d00001 diff --git a/dist/app/fonts/glyphicons-halflings-regular.svg b/dist/app/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..e3e2dc7 --- /dev/null +++ b/dist/app/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dist/app/fonts/glyphicons-halflings-regular.ttf b/dist/app/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..67fa00bf83801d2fa568546b982c80d27f6ef74e GIT binary patch literal 41280 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

    PBK;&Wcg|NX{>QR@W3{K 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 literal 0 HcmV?d00001 diff --git a/dist/app/fonts/glyphicons-halflings-regular.woff b/dist/app/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..8c54182aa5d4d1ab3c9171976b615c1dcb1dc187 GIT binary patch literal 23320 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 literal 0 HcmV?d00001 diff --git a/dist/app/index.html b/dist/app/index.html new file mode 100644 index 0000000..ee2da91 --- /dev/null +++ b/dist/app/index.html @@ -0,0 +1,75 @@ + + + + + + Puppet Explorer + + + + + + + + + + + + + + + + + + + +

    +
    + + +
    + + + + + + +
    + + diff --git a/dist/app/services/puppetdb/puppetdb.js b/dist/app/services/puppetdb/puppetdb.js new file mode 100644 index 0000000..ed745c8 --- /dev/null +++ b/dist/app/services/puppetdb/puppetdb.js @@ -0,0 +1,165 @@ +/* global angular PUPPETDB_SERVERS */ +import puppetdbquery from 'node-puppetdbquery'; +angular.module('app').factory('PuppetDB', ($http, $location, $q) => + new class PuppetDB { + constructor() { + this.apiVersion = 'v4'; + this.cancel = this.cancel.bind(this); + this.servers = PUPPETDB_SERVERS.map(srv => srv[0]); + this.canceller = $q.defer(); + } + + // Public: Get/Set server + // + // server - The String name of the server to set + // + // Returns: the String current server + server(server) { + if (server) { + return $location.search('server', server); + } + return $location.search().server || PUPPETDB_SERVERS[0][0]; + } + + // Public: Get URL of current server + // + // Returns: The {String} URL + serverUrl() { + for (const server of PUPPETDB_SERVERS) { + if (server[0] === this.server()) { return server[1]; } + } + throw new Error('Server config not found'); + } + + // Public: Get config properties of current server + // + // Returns: The {Object} config + serverConfig() { + for (const server of PUPPETDB_SERVERS) { + if (server[0] === this.server()) { + if (server[2]) { + return server[2]; + } + return ({}); + } + } + throw new Error('Server config not found'); + } + + // Public: Parse a query + // + // query - The {String} query to parse + // + // Returns: The resulting query + parse(query) { + if (query) { + return puppetdbquery.parse(query); + } + return null; + } + + // Public: Basic function to get a path from current server + // + // path - The {String} path to get + // params - A {Object} with query parameters + // + // Returns: A promise from $http + get(path, params = {}) { + const config = this.serverConfig(); + config.params = params; + config.timeout = this.canceller.promise; + return $http.get(`${this.serverUrl()}/${path}`, config); + } + + // Like get, but for current API query path + getQuery(path, params = {}) { + return this.get(`pdb/query/${this.apiVersion}/${path}`, params); + } + + // Public: Query a endpoint and handle parsing return data and errors + // + // endpoint - The {String} endpoint to query + // params - The {Object} query parameters + query(endpoint, query, params = {}, success) { + params.query = angular.toJson(query); + return this.handleResponse(this.getQuery(endpoint, params), success); + } + + // Combine queries together + combine(...queries) { + const actualQueries = queries.filter(q => q != null); + if (actualQueries.length === 0) { + return null; + } else if (actualQueries.length === 1) { + return actualQueries[0]; + } + return ['and', ...actualQueries]; + } + + // Generically handle a response on a HTTP promise object + // + // promise - {HttpPromise} + // success - {Function} that takes a data and optional total argument + handleResponse(promise, success) { + return promise.then( + (resp) => success(angular.fromJson(resp.data, resp.headers('X-Records'))), + (resp) => { + if (resp.status > 0) { + throw new Error(`Failed to fetch ${resp.config.url} +${resp.status}: ${resp.statusText}`); + } + } + ); + } + + cancel() { + this.canceller.resolve('user cancelled'); + this.canceller = $q.defer(); + } + + // Public: Combined function to both parse and query PuppetDB. + // + // endpoint - The {String} endpoint to query + // nodeQuery - The {String} query to parse + // additionalQuery - The {Array} a raw API query that is added to the nodeQuery + // params - The {Object} additional parameters to the endpoint + // success - The {Function} callback on success + // + // Returns: The {Promise} from $http + parseAndQuery(endpoint, nodeQuery, additionalQuery, params = {}, success) { + // Handle all the parsing of the query and putting them together + let query = this.combine(this.parse(nodeQuery), additionalQuery); + if (!query) { // no query and no additional query either + if (['reports', 'events', 'event-count', 'aggregate-event-count'].includes(endpoint)) { + // PuppetDB really requires a valid query for some endpoints even + // if we want to query all. So we just make something up that will + // match all of them... + query = ['>', 'timestamp', '1970-01-01T00:00:00.000Z']; + } + } + + if (['nodes', 'reports', 'events', 'facts'].includes(endpoint)) { + params.include_total = true; + } + + // Start querying + return this.query(endpoint, query, params, success); + } + + // Public: Get a bean value, returns a promise just like $http + // + // name - The {String} bean name + // + // Returns: A promise from $http + getBean(name) { + return this.get(`metrics/v1/mbeans/${name}`); + } + + // Public: Get PuppetDB version, returns a promise just like $http + // + // Returns: A promise from $http + getVersion() { + return this.get('pdb/meta/v1/version'); + } + }() +); diff --git a/dist/config.js b/dist/config.js new file mode 100644 index 0000000..d671118 --- /dev/null +++ b/dist/config.js @@ -0,0 +1,19 @@ +// List of PuppetDB servers, pairs of name and URL +// The first one will be used as the default server +PUPPETDB_SERVERS = [["production","/api"]]; + +// A list of important facts that you want shown in the node detail view +NODE_FACTS = ["operatingsystem","operatingsystemrelease","manufacturer","productname","processorcount","memorytotal","ipaddress"]; + +// The amount of hours since the last check-in after which a node is considered unresponsive +UNRESPONSIVE_HOURS = 2; + +// Customisable dashboard panels +// type can be either 'primary', 'success', 'info', 'warning' or 'danger' +// Different types will be displayed with different colors +DASHBOARD_PANELS = [{"name":"Unresponsive nodes","type":"danger","query":"#node.report_timestamp < @\"now - 2 hours\""},{"name":"Nodes in production env","type":"success","query":"#node.catalog_environment = production"},{"name":"Nodes in non-production env","type":"warning","query":"#node.catalog_environment != production"}]; + +// Google Analytics settings +GA_TRACKING_ID = 'UA-XXXXXXXX-YY'; +GA_DOMAIN = 'auto'; + diff --git a/dist/config.js.example b/dist/config.js.example new file mode 100644 index 0000000..983fd4f --- /dev/null +++ b/dist/config.js.example @@ -0,0 +1,46 @@ +// List of PuppetDB servers, pairs of name, URL and $http config object +// The first one will be used as the default server +PUPPETDB_SERVERS = [ + ['production', '/api'], + ['testing', '/api'] +]; + +// A list of important facts that you want shown in the node detail view +NODE_FACTS = [ + 'operatingsystem', + 'operatingsystemrelease', + 'manufacturer', + 'productname', + 'processorcount', + 'memorytotal', + 'ipaddress' +]; + +// The amount of hours since the last check-in after which a node is considered +// unresponsive +UNRESPONSIVE_HOURS = 2; + +// Customisable dashboard panels +// type can be either 'primary', 'success', 'info', 'warning' or 'danger' +// Different types will be displayed with different colors +DASHBOARD_PANELS = [ + { + name: 'Unresponsive nodes', + type: 'danger', + query: '#node.report_timestamp < @"now - 2 hours"' + }, + { + name: 'Nodes in production env', + type: 'success', + query: '#node.catalog_environment = production' + }, + { + name: 'Nodes in non-production env', + type: 'warning', + query: '#node.catalog_environment != production' + } +]; + +// Google Analytics settings +GA_TRACKING_ID = 'UA-XXXXXXXX-YY'; +GA_DOMAIN = 'auto'; diff --git a/dist/controllers/dashboard/dashboard.tpl.html b/dist/controllers/dashboard/dashboard.tpl.html new file mode 100644 index 0000000..71fbb56 --- /dev/null +++ b/dist/controllers/dashboard/dashboard.tpl.html @@ -0,0 +1,114 @@ +
    +
    +
    +

    Nodes

    +
    {{activeNodes}}
    +
    +
    +
    +
    +

    Resources

    +
    {{resources}}
    +
    +
    +
    +
    +

    Avg resources/node

    +
    {{avgResources}}
    +
    +
    +
    +
    +

    Resource duplication %

    +
    {{resDuplication}}
    +
    +
    +
    + +
    +
    +
    +

    {{panel.name}}

    +
    {{panel.count}}
    +
    +
    +
    + +
    +
    +

    Syntax

    +

    Use fact=value to search for nodes where fact equals value. + To search for structured facts use dots between each part of the fact path, for example + foo.bar=baz.

    + +

    Resources can be matched using the syntax type[title]{param=value}. + The part in brackets is optional. You can also specify ~ before the title + to do a regexp match on the title. Type names and class names are case insensitive. + A resource can be preceded by @@ to match exported resources, the default is to only + match "local" resources.

    + +

    Strings can contain letters, numbers or the characters :-_ without needing to be quoted. + If they contain any other characters they need to be quoted with single or double quotes. + Use backslash (\) to escape quotes within a quoted string or double backslash for backslashes.

    + +

    An unquoted number or the strings true/false will be interpreted as numbers and boolean + values, use quotation marks around them to search for them as strings instead.

    + +

    A @ sign before a string causes it to be interpreted as a date parsed with + timespec. + For example @"now - 2 hours".

    + +

    A # sign can be used to do a subquery, against the nodes endpoint for example to + query the report_timestamp, catalog_timestamp or facts_timestamp + fields. For example #node.report_timestamp < @"now - 2 hours".

    + +

    A subquery using the # sign can have a block of expressions instead of a single + expression. For example #node { report_timestamp > @"now - 4 hours" and + report_timestamp < @"now - 2 hours" }

    + +

    A bare string without comparison operator will be treated as a regexp match against the certname.

    +
    + +
    +

    Comparison operators

    + + + + + + + + + + +
    =Equality
    !=Not equal
    ~Regexp match
    !~Not regexp match
    <Less than
    <=Less than or equal
    >Greater than
    >=Greater than or equal
    + +

    Logical operators

    + + + + +
    not(unary op)
    and
    or
    +

    Shown in precedence order from highest to lowest. Use parenthesis to change order in an expression.

    +
    + +
    +

    Query Examples

    + Nodes with package mysql-server and amd64 arcitecture +
    + package["mysql-server"] and architecture=amd64 +
    + Nodes with the class Postgresql::Server and a version set to 9.3 +
    + class[postgresql::server]{ version="9.3" } +
    + Nodes with 4 or 8 processors running Linux +
    + (processorcount=4 or processorcount=8) and kernel=Linux +
    + Nodes that haven't reported in the last 2 hours +
    + #node.report_timestamp<@"now - 2 hours" +
    +
    +
    diff --git a/dist/controllers/events/events.tpl.html b/dist/controllers/events/events.tpl.html new file mode 100644 index 0000000..b6850e7 --- /dev/null +++ b/dist/controllers/events/events.tpl.html @@ -0,0 +1,129 @@ + + + +
    +
    +
    + +
    + + + + +
    +
    +
    +
    +
    + +
    + + + + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    +

    Containing class

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Resource type

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Event status

    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NodeTypeTitlePropertyFromTo
    {{event.certname}}{{event.resource_type}}{{event.resource_title}}{{event.property}}{{event.old_value}}{{event.new_value}}
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Message{{event["message"]}}
    Containing-class{{event.containing_class}}
    Containment-path + +
    File{{event["file"]}}:{{event["line"]}}
    Timestamp
    Report{{event.report}}
    +
    + + diff --git a/dist/controllers/facts/facts.tpl.html b/dist/controllers/facts/facts.tpl.html new file mode 100644 index 0000000..e07431d --- /dev/null +++ b/dist/controllers/facts/facts.tpl.html @@ -0,0 +1,31 @@ +
    +
    +
    + + +
    + + + + + + + +
    + {{factCompontent}} / +
    +
    + +
    +
    +
    + +

    {{chart}}

    +
    +
    +
    +
    +
    +
    +
    diff --git a/dist/controllers/nodedetail/nodedetail.tpl.html b/dist/controllers/nodedetail/nodedetail.tpl.html new file mode 100644 index 0000000..f5dd405 --- /dev/null +++ b/dist/controllers/nodedetail/nodedetail.tpl.html @@ -0,0 +1,71 @@ + +
    +
    + +
    +
    Reports
    + + + + + + + + + + + + + + + + + + + +
    TimeSuccessesNoopsSkipsFailures
    + + {{report.events.successes || ""}}{{report.events.noops || ""}}{{report.events.skips || ""}}{{report.events.failures || ""}} + +
    + +
    +
    +
    +
    +
    Facts
    + + + + + + + + + + + +
    FactValue
    {{factCompontent}} / {{fact.value}}
    +
    + +
    +
    All facts
    + + + + + + + + + + + +
    FactValue
    {{factCompontent}} / {{fact.value}}
    +
    +
    +
    diff --git a/dist/controllers/nodelist/nodelist.tpl.html b/dist/controllers/nodelist/nodelist.tpl.html new file mode 100644 index 0000000..d7c77a0 --- /dev/null +++ b/dist/controllers/nodelist/nodelist.tpl.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    Last runSuccessesNoopsSkipsFailures
    {{node.certname}} + + + {{node.metrics.events.success || ""}}{{node.metrics.events.noop || ""}}{{node.metrics.events.skip || ""}}{{node.metrics.events.failure || ""}} + +
    + + diff --git a/dist/css/bootstrap.min.css b/dist/css/bootstrap.min.css new file mode 100644 index 0000000..e2596a7 --- /dev/null +++ b/dist/css/bootstrap.min.css @@ -0,0 +1,11 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700");/*! + * bootswatch v3.3.7 + * Homepage: http://bootswatch.com + * Copyright 2012-2016 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#3e3f3a;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#93c54b;text-decoration:none}a:hover,a:focus{color:#79a736;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#f8f5f0;border:1px solid #dfd7ca;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #f8f5f0}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#98978b}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#98978b}.text-primary{color:#325d88}a.text-primary:hover,a.text-primary:focus{color:#244363}.text-success{color:#93c54b}a.text-success:hover,a.text-success:focus{color:#79a736}.text-info{color:#29abe0}a.text-info:hover,a.text-info:focus{color:#1b8dbb}.text-warning{color:#f47c3c}a.text-warning:hover,a.text-warning:focus{color:#ef5c0e}.text-danger{color:#d9534f}a.text-danger:hover,a.text-danger:focus{color:#c9302c}.bg-primary{color:#fff;background-color:#325d88}a.bg-primary:hover,a.bg-primary:focus{background-color:#244363}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #f8f5f0}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #98978b}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #dfd7ca}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#3e3f3a}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #dfd7ca;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#8e8c84;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#98978b;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #dfd7ca}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #dfd7ca}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #dfd7ca}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #dfd7ca}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #dfd7ca}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f8f5f0}.table-hover>tbody>tr:hover{background-color:#f8f5f0}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f8f5f0}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#f0e9df}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #dfd7ca}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:inherit;border:0;border-bottom:1px solid transparent}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:13px;font-size:14px;line-height:1.42857143;color:#3e3f3a}.form-control{display:block;width:100%;height:46px;padding:12px 16px;font-size:14px;line-height:1.42857143;color:#3e3f3a;background-color:#ffffff;background-image:none;border:1px solid #dfd7ca;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:transparent;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6)}.form-control::-moz-placeholder{color:#dfd7ca;opacity:1}.form-control:-ms-input-placeholder{color:#dfd7ca}.form-control::-webkit-input-placeholder{color:#dfd7ca}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#f8f5f0;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:46px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:66px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:13px;padding-bottom:13px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:66px;padding:20px 30px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:66px;line-height:66px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:66px;padding:20px 30px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:66px;line-height:66px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:66px;min-height:38px;padding:21px 30px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:57.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:46px;height:46px;line-height:46px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:66px;height:66px;line-height:66px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#93c54b}.has-success .form-control{border-color:#93c54b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#79a736;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c1de98;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c1de98}.has-success .input-group-addon{color:#93c54b;border-color:#93c54b;background-color:#dff0d8}.has-success .form-control-feedback{color:#93c54b}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#f47c3c}.has-warning .form-control{border-color:#f47c3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#ef5c0e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f9bd9d;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f9bd9d}.has-warning .input-group-addon{color:#f47c3c;border-color:#f47c3c;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#f47c3c}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#d9534f}.has-error .form-control{border-color:#d9534f;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#c9302c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #eba5a3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #eba5a3}.has-error .input-group-addon{color:#d9534f;border-color:#d9534f;background-color:#f2dede}.has-error .form-control-feedback{color:#d9534f}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#7f8177}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:13px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:33px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:13px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:21px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:12px 16px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#ffffff;background-color:#3e3f3a;border-color:transparent}.btn-default:focus,.btn-default.focus{color:#ffffff;background-color:#242422;border-color:rgba(0,0,0,0)}.btn-default:hover{color:#ffffff;background-color:#242422;border-color:rgba(0,0,0,0)}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#ffffff;background-color:#242422;border-color:rgba(0,0,0,0)}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#ffffff;background-color:#121210;border-color:rgba(0,0,0,0)}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#3e3f3a;border-color:transparent}.btn-default .badge{color:#3e3f3a;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#325d88;border-color:transparent}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#244363;border-color:rgba(0,0,0,0)}.btn-primary:hover{color:#ffffff;background-color:#244363;border-color:rgba(0,0,0,0)}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#244363;border-color:rgba(0,0,0,0)}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#1b3249;border-color:rgba(0,0,0,0)}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#325d88;border-color:transparent}.btn-primary .badge{color:#325d88;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#93c54b;border-color:transparent}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#79a736;border-color:rgba(0,0,0,0)}.btn-success:hover{color:#ffffff;background-color:#79a736;border-color:rgba(0,0,0,0)}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#79a736;border-color:rgba(0,0,0,0)}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#658c2d;border-color:rgba(0,0,0,0)}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#93c54b;border-color:transparent}.btn-success .badge{color:#93c54b;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#29abe0;border-color:transparent}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#1b8dbb;border-color:rgba(0,0,0,0)}.btn-info:hover{color:#ffffff;background-color:#1b8dbb;border-color:rgba(0,0,0,0)}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#1b8dbb;border-color:rgba(0,0,0,0)}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#17759c;border-color:rgba(0,0,0,0)}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#29abe0;border-color:transparent}.btn-info .badge{color:#29abe0;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f47c3c;border-color:transparent}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#ef5c0e;border-color:rgba(0,0,0,0)}.btn-warning:hover{color:#ffffff;background-color:#ef5c0e;border-color:rgba(0,0,0,0)}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#ef5c0e;border-color:rgba(0,0,0,0)}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#ce4f0c;border-color:rgba(0,0,0,0)}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f47c3c;border-color:transparent}.btn-warning .badge{color:#f47c3c;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#d9534f;border-color:transparent}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#c9302c;border-color:rgba(0,0,0,0)}.btn-danger:hover{color:#ffffff;background-color:#c9302c;border-color:rgba(0,0,0,0)}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#c9302c;border-color:rgba(0,0,0,0)}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#ac2925;border-color:rgba(0,0,0,0)}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:transparent}.btn-danger .badge{color:#d9534f;background-color:#ffffff}.btn-link{color:#93c54b;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#79a736;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#dfd7ca;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:20px 30px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#ffffff;border:1px solid #dfd7ca;border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#f8f5f0}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#98978b;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#98978b;background-color:#f8f5f0}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#98978b;text-decoration:none;outline:0;background-color:#f8f5f0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#dfd7ca}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#dfd7ca;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:66px;padding:20px 30px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:66px;line-height:66px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:12px 16px;font-size:14px;font-weight:normal;line-height:1;color:#3e3f3a;text-align:center;background-color:#f8f5f0;border:1px solid #dfd7ca;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:20px 30px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#f8f5f0}.nav>li.disabled>a{color:#dfd7ca}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#dfd7ca;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#f8f5f0;border-color:#93c54b}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #dfd7ca}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#dfd7ca #dfd7ca #dfd7ca}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#98978b;background-color:#ffffff;border:1px solid #dfd7ca;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #dfd7ca}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #dfd7ca;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#98978b;background-color:#f8f5f0}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #dfd7ca}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #dfd7ca;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:60px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:20px 15px;font-size:18px;line-height:20px;height:60px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:13px;margin-bottom:13px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:10px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:20px;padding-bottom:20px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:7px;margin-bottom:7px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:7px;margin-bottom:7px}.navbar-btn.btn-sm{margin-top:15px;margin-bottom:15px}.navbar-btn.btn-xs{margin-top:19px;margin-bottom:19px}.navbar-text{margin-top:20px;margin-bottom:20px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#3e3f3a;border-color:#3e3f3a}.navbar-default .navbar-brand{color:#ffffff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-default .navbar-text{color:#8e8c84}.navbar-default .navbar-nav>li>a{color:#98978b}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#ffffff;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#393a35}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:transparent}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#393a35}.navbar-default .navbar-toggle .icon-bar{background-color:#98978b}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#3e3f3a}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#393a35;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#98978b}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#393a35}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#98978b}.navbar-default .navbar-link:hover{color:#ffffff}.navbar-default .btn-link{color:#98978b}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#ffffff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#93c54b;border-color:#93c54b}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-inverse .navbar-text{color:#dfd7ca}.navbar-inverse .navbar-nav>li>a{color:#6b9430}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#89be3d}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:transparent}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#89be3d}.navbar-inverse .navbar-toggle .icon-bar{background-color:#6b9430}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#81b33a}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#89be3d;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#93c54b}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#93c54b}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#6b9430}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#89be3d}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444444;background-color:transparent}}.navbar-inverse .navbar-link{color:#6b9430}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#6b9430}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f8f5f0;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#dfd7ca}.breadcrumb>.active{color:#98978b}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:12px 16px;line-height:1.42857143;text-decoration:none;color:#98978b;background-color:#f8f5f0;border:1px solid #dfd7ca;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#8e8c84;background-color:#dfd7ca;border-color:#dfd7ca}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#8e8c84;background-color:#dfd7ca;border-color:#dfd7ca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#dfd7ca;background-color:#f8f5f0;border-color:#dfd7ca;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:20px 30px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#f8f5f0;border:1px solid #dfd7ca;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#dfd7ca}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#dfd7ca;background-color:#f8f5f0;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#3e3f3a}.label-default[href]:hover,.label-default[href]:focus{background-color:#242422}.label-primary{background-color:#325d88}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#244363}.label-success{background-color:#93c54b}.label-success[href]:hover,.label-success[href]:focus{background-color:#79a736}.label-info{background-color:#29abe0}.label-info[href]:hover,.label-info[href]:focus{background-color:#1b8dbb}.label-warning{background-color:#f47c3c}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ef5c0e}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#93c54b;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#ffffff;background-color:#93c54b}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#f8f5f0}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e8decd}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#f8f5f0;border:1px solid #dfd7ca;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#93c54b}.thumbnail .caption{padding:9px;color:#3e3f3a}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#93c54b;border-color:transparent;color:#ffffff}.alert-success hr{border-top-color:rgba(0,0,0,0)}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#29abe0;border-color:transparent;color:#ffffff}.alert-info hr{border-top-color:rgba(0,0,0,0)}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f47c3c;border-color:transparent;color:#ffffff}.alert-warning hr{border-top-color:rgba(0,0,0,0)}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#d9534f;border-color:transparent;color:#ffffff}.alert-danger hr{border-top-color:rgba(0,0,0,0)}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#325d88;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#93c54b}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#29abe0}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f47c3c}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #dfd7ca}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#3e3f3a}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:inherit}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#3e3f3a;background-color:#f8f5f0}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#f8f5f0;color:#dfd7ca;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#dfd7ca}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#3e3f3a;background-color:#f8f5f0;border-color:#dfd7ca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#3e3f3a}.list-group-item-success{color:#93c54b;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#93c54b}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#93c54b;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#93c54b;border-color:#93c54b}.list-group-item-info{color:#29abe0;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#29abe0}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#29abe0;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#29abe0;border-color:#29abe0}.list-group-item-warning{color:#f47c3c;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#f47c3c}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#f47c3c;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.list-group-item-danger{color:#d9534f;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#d9534f}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#d9534f;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#d9534f;border-color:#d9534f}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f8f5f0;border-top:1px solid #dfd7ca;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #dfd7ca}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #dfd7ca}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #dfd7ca}.panel-default{border-color:#dfd7ca}.panel-default>.panel-heading{color:#3e3f3a;background-color:#f8f5f0;border-color:#dfd7ca}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#dfd7ca}.panel-default>.panel-heading .badge{color:#f8f5f0;background-color:#3e3f3a}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#dfd7ca}.panel-primary{border-color:#325d88}.panel-primary>.panel-heading{color:#ffffff;background-color:#325d88;border-color:#325d88}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#325d88}.panel-primary>.panel-heading .badge{color:#325d88;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#325d88}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#93c54b;background-color:#93c54b;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#93c54b;background-color:#93c54b}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#29abe0;background-color:#29abe0;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#29abe0;background-color:#29abe0}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#f47c3c;background-color:#f47c3c;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#f47c3c;background-color:#f47c3c}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#d9534f;background-color:#d9534f;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#d9534f;background-color:#d9534f}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f8f5f0;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000000;text-shadow:0 0 0 transparent;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #f8f5f0;border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #f8f5f0}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #f8f5f0}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:1;filter:alpha(opacity=100)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#3e3f3a;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#3e3f3a}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#3e3f3a}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#3e3f3a}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#3e3f3a}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#3e3f3a}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#3e3f3a}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#3e3f3a}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#3e3f3a}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#ffffff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #dfd7ca;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f8f5f0;border-bottom:1px solid #f0e9df;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#b9a78a;border-top-color:#dfd7ca;bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#b9a78a;border-right-color:#dfd7ca}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#b9a78a;border-bottom-color:#dfd7ca;top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#b9a78a;border-left-color:#dfd7ca}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.sandstone{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.navbar .nav>li>a{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.navbar-form input,.navbar-form .form-control{border:none}.btn{border:none;font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.btn:hover{border-color:transparent}.btn-lg{line-height:26px}.btn-default:hover{background-color:#393a35}input,.form-control{-webkit-box-shadow:none;box-shadow:none}input:focus,.form-control:focus{border-color:#dfd7ca;-webkit-box-shadow:none;box-shadow:none}.nav{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:#dfd7ca}.nav-tabs>li>a{background-color:#f8f5f0;border-color:#dfd7ca;color:#98978b}.nav-tabs>li.disabled>a:hover{background-color:#f8f5f0}.nav-pills a{color:#98978b}.nav-pills li>a{border:1px solid transparent}.nav-pills li.active>a,.nav-pills li>a:hover{border-color:#dfd7ca}.nav-pills li.disabled>a{border-color:transparent}.breadcrumb{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase;border:1px solid #dfd7ca}.breadcrumb a{color:#98978b}.pagination{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.pager{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.pager li>a{color:#98978b}.dropdown-menu>li>a{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.alert a,.alert .alert-link{color:#fff}.tooltip{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.progress{border-radius:10px;background-color:#dfd7ca;-webkit-box-shadow:none;box-shadow:none}.progress-bar{-webkit-box-shadow:none;box-shadow:none}.list-group-item{padding:16px 24px}.well{-webkit-box-shadow:none;box-shadow:none}.panel{-webkit-box-shadow:none;box-shadow:none}.panel .panel-heading,.panel .panel-title{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase;color:#fff}.panel .panel-footer{font-size:11px;line-height:22px;font-weight:500;text-transform:uppercase}.panel-default .panel-heading,.panel-default .panel-title,.panel-default .panel-footer{color:#98978b} \ No newline at end of file diff --git a/dist/css/style.css b/dist/css/style.css new file mode 100644 index 0000000..7484f62 --- /dev/null +++ b/dist/css/style.css @@ -0,0 +1,44 @@ +.table-hover, .nav, .pagination, .carousel, .panel-title a, [ng-click] { cursor: pointer; } + +.reveal-animation.ng-enter { + -webkit-animation: enter_sequence 0.4s linear; /* Safari/Chrome */ + animation: enter_sequence 0.4s linear; /* IE10+ and Future Browsers */ +} +@-webkit-keyframes enter_sequence { + from { opacity:0; } + to { opacity:1; } +} +@keyframes enter_sequence { + from { opacity:0; } + to { opacity:1; } +} + +.reveal-animation.ng-leave { + -webkit-animation: leave_sequence 0.2s linear; /* Safari/Chrome */ + animation: leave_sequence 0.2s linear; /* IE10+ and Future Browsers */ +} +@-webkit-keyframes leave_sequence { + from { opacity:1; } + to { opacity:0; } +} +@keyframes leave_sequence { + from { opacity:1; } + to { opacity:0; } +} + +.spin{ + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + -webkit-animation: spin 1s infinite linear; + -moz-animation: spin 1s infinite linear; + -o-animation: spin 1s infinite linear; + animation: spin 1s infinite linear; +} +@-webkit-keyframes spin { + from {-webkit-transform: rotate(0deg);} + to {-webkit-transform: rotate(359deg);} +} +@keyframes spin { + from {transform: rotate(0deg);} + to {transform: rotate(359deg);} +} diff --git a/dist/favicon.ico b/dist/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..85b071ac3c6e816a5c6efab514f5adc5fcdf2f20 GIT binary patch literal 1150 zcmajbODIHf6vy%75nR#e(aanOz@h z0tf75!-EFfO&UdQ;gKN_GpBDKM%%TY)KLhJvJcxKtMc!N@h9TmM f7WwpwypM_8E{L3SR&MdUEAo;|c7^OS%0<2aCoBf| literal 0 HcmV?d00001 diff --git a/dist/fonts/glyphicons-halflings-regular.eot b/dist/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..4a4ca865d67e86f961bc6e2ef00bffa4e34bb9ed GIT binary patch literal 20335 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

    !oRptb@n+ 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 literal 0 HcmV?d00001 diff --git a/dist/fonts/glyphicons-halflings-regular.svg b/dist/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..e3e2dc7 --- /dev/null +++ b/dist/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dist/fonts/glyphicons-halflings-regular.ttf b/dist/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..67fa00bf83801d2fa568546b982c80d27f6ef74e GIT binary patch literal 41280 zcmc${2b>$#wLd<0X4JKkMs=IoY9(#guC%-Ix~!LV@5XgawLzwtVoFRi&4B<;Yzzq| z1QHw)z@da0*@PsIyqA!`6G@b6oWOe_b_$P#@)GbXG2Zd-d+unfZAkvV-{LBX3Wc;?Pswd9i3FaAXkSUrx`&zn7GF0_`M^SUUB}0?t9iO6@<@rQX4MYaNTB6W_twTb8q4L*yS58+j!vF z2j3Nh`>lc?ZQXpu)z^G$?&B8=!spQk>+PGb+PGPLztt}YU&eW%aO!9EjS$4lmWxSf0(+a;I;S#pX$!?81r zPxe(ID}q`APM!R3^`f;)g#n@JcY^fY+Km6eDgyYBYd&V!e;1`7xevutA z9r7HC9qK$ZaA-Mx@w`Ku58Zlb*I{&GuRWclsyf4l#;7ri09Ui*6RHTP@wSWT=t=8ZXH=9myY8a)#IAo_0fKca`D z*F~?2UK+h1x;}btbX|01bV+nx^t9+egvQ|i`5yx>jQlJU@$>W=|A&(_6vm%?s-YdZ z;Q!}OV(bZjm;rz1-#tQ;_`j;qrV74A>f+@?>cTDSR3S05S~a&0%~;2e-Lx)tKxMv; z>UNd2#a>sPt?jDVwrIuBoW#0#yDGI^Tpd#fmJh|%fpzVw+(uuGC*n5@{id$Gt`64? z4cEQ9t}YQ*O|3)f+%4<)iFNDnd#1Lkv(9K&&23r(y9;-Z-F4Pkb*g}$v9xK8{LsMY zA#0mgiS=dLRa;x^Cc4QF@cS`UN-jvmR5`U!6_yWe-?)84j5em!#pCPhw)4Fe#va|! zZnVx*=ZWJcj<(n@cz2v_v5abIJ!>cyo0pio;gZ-;tZ<(36Leh_-5IxzZI8{{K6gW6 zdu)4x-!7pFD~8koT#5eCZPkH|w1e-s_?>1Ptd7U)Vh6W_4EWLlv~6{zZD=1ZbGId8 z2P-#E#D*5Ftc$B`-OzS)XhC9oBDQ_O_QVEi33Z3wsXZPV1}}y|p$^c7cTxw?(8S!t zhD+9u?+Ja?*M?4Pzmv$eu#nhpQDe)8rq_KJXZ&sZgaI}%ILH=#(<7WO@OQd+HCi6q zzG5hG9$KFmtiuOO41)3lD~5_fOqg~4V3EZbKGfLxYR$%a-ctNxpiRY5&;@Vp#E_7w zkT-73wkGUcB*ievEJBCIgv|7!MHb)9YG%{FPcKR$HU&+h!zMahw3wx1(~FFb=ajgT z%qfW`HlV-tm%m7{V~3g`k(p2s3i4uku@Dj(1y#tXRXLTFRY#Vo)fv@yP&H*$Z&|fu zwHnqcbawfA;^}-y$tn4eB_4=}ENLa7Skn0dlb+x4dBA$NMe@P+tN3)UA)gG`7`p@g}ksuP_r4esa$Nz(oZ#Y*myhQ zydBZ3YRahfIn`WNYqM$~qdLmPfP*d!c&KGlGHRZ;tf8!hquH$5;L+MytLn+B9c9&> z)%sYg){s}cs-;hDSBj2Uwy&>`sF=@n=M(u{Z@xE|4FyAq?hY~0;1VryOWYj5TSU%f z`^BD|*kB}m6&MwIx%*C_4-Kj)_rGq6J%mIJM#ave| z6W_b;$tSPtXlr}!^3VTT99+%bTYl9u??3I@aP6-itZ}+F;Z~$u6l4`VD`Otmv91d} zER<(S#b#32t`d6j;d0id9}tJcA&h=ofez}MOMLIh@MGecx|6jH@5S#($3Hm!f&3l$ zJD6Q&(h@95us6di-`kyGsRm0GTk_j84vH5XTyyaJs;URwjqa+=zdhYJa8^~?^^8KtwNh&Fei-jtC-6@O7#R52HmK*O{ zb{aZAuyEO0ulKHHb62|T!ydZ}`=7qNxi+xAMLg%B;s5c3YOm_eH`jzt&r4U@9n$wC zpM7|lQe8tUd+7K(@(<((1)oqStP_e*@>*4IMh%tKx(s^5)cTCd4yu8&8t{;8P)(Qv zVE3AU;@u~S9&cl)PcOVYDiH%eQKR|9}_GlobT-NdeEVO-@<}^H#0Y+ z8Q5L)1Y^CPR4l~m!D{tOS)0XjnbmLA4_v#m^vM^Q_j}*d-(&C6IsFf%o!9CIaPl&X zg|#geFV+9@;`eX`hJ?@aA^BN(won6(WNK|j6%Gd{TZs`|W+=eeBozwtMwk^=|gMSwn`IzBM5z3t%CUFVn_xPg)&+-Z}Nm+_k}F^P&%JTTTZ;stRF1+?)Mjd z@9iZ^PjW}`nw`J<%#J^P=9j)n&CF?*>`C{+zjvK zuNOv-VW}N|3CU6jr(;`3FW{u)Z?q=6LBotNQy3JAAabkPmIDEaWZ{fDos*^;yfMJ( zfi(x~V>RAAS`5<>L~AaqQ?lA=oNs!R?p{dTU_il`#v4*K7~%2z>|@S{!3BYEIG}H) z_pxnpX#C#z?d;e^VeztYJHy`@w=?040O^T8t{05-eVK5saD{M-a1YjMP6ciHrCKltrL=JU^%w? z%G&%P`t)e)acuLg*uJ=|U3XVDtKG{fM{{8sGiF08Ye*?QAHB~$=KSRE|D)H310@=Q zQ@pWVr#!_^eBAl$=-)<^As zJhjCaXt;)F)BDM{$J2alXh-S%@f4-CE-W<2@5?O&s9@VPh1%VaGs>!k%%NCOX!q7hU38p|b zovTxd{u+j_eYEZ&L7wLVxj-V2==n%JWNx8UD3m@%8`0O%MTNo`?Y_YEs;F@G1lm<7 z6B|dFie`mXi)&WTk!DpN9@opsy47=}Th&KCR=bk0jD2*^NKaw!Rn)8<*XyrZg3!aP zBWl)*%=02T#&ty@BtHoKp$@D49Dxi+JJ#tozAjnHMJVYQMGK5M)#A~d7;9g-==9M+ zC+sLPnKY*bgA}T+PoUvsAa#550cf*+sDeG+sdP`!3k^+d=n$DPfw7($6FBsXCobH2 zl%02U>xEDJ;>?F$edpDO&Sbv{2MRQk@FosD&zkxl&zG*#jvm#nE9D>W*MI%|7F>mk znUk(EmLpgb1%W{>X`^~fr%;5k(W+UUxg1kH8C5<=T0J^pMJF6Ela21U%bLQaO&%6D zgK<3auK;7Dt%RX3F)~Ql5#33aHxvaxlcG>7)XBT$-NHQKbm2UK)a&JCbx}s`1@%^N z>dh~!^F7)U+zkubO3-P(KsMA2u>BHcpF5E2BUWhiYBd=cmfCW#yk>y{qb^eRN%8a? zI@{~jT2CW}_xYn@Fv={!P(BpIW-dEZ?48L%z4>&$7n?oZ88MY%`Bd7HPGK|A;1YEiG@Keut^O%am$rsLQ0x9U0T7rgScss@?4KCe!Dc zCnPOzoBkzKkurMPR~sJlqu6;PIcA{-F)-Vx|?r? z`d|?X$B)aZ$q&7MOasjecMHWhX;F=^_B*??Sm@K4VoSC+2X&#Y3>A}<3RfGBXENMw zg?V3lkXD^WkCwy`019a$&9s)?Cn=eC2St6RCAO;o}h)=XB2SH>r+jiH(R9}{

    PBK;&Wcg|NX{>QR@W3{K zY;bp3^^^Hp4EgCcp#a7O7KV(e2E!07sKTguG(W~^?4lZ66!OsI#=Iw^QS(LZUvY)|-*On%Um?5>WA zl?50LJ%&XEbBcfmH}zOz=!^;alP6P=Rtc7q@Q=l%gyhRfi2{4}=YdE4KV#1hzuEkL zQ`e!oCxJ!)KmnXWYrzo%_u;5NbadmMK<}VRv{vp06NK?w7^1Q$Tj1RM!76dG8csvB z!8uB~T2M}Lf-thpE(M7RjA_gX6%1j2BB6X0eI$mNZ8{a1K44Q>^W@3P_G84KehO22 zJG-|8&J9&`rg~weKrl1JkCIVq&`ucl7;DHYw@0%Zyc$6}?KFTU+2;?{&=A`cEfAzN zU!jp_g3S-`18T6M@<#h3A_2$=zd4rj5XfwaD;BKizzZu%((a@Bm!J{db@_d4*S%kS z85)uJ6H=aVdJ9w~XjG@unH$c0h>vFo<4HQ6M~DkI2t|eFJmy!hTnt8Ojt6To$AMXy z%Ec-Z9jL;jXKDjiV*u!Qj44=K))MH9htwFwi|JpZJZ~{M?9ff()c#tpX0uYaf>A6l zaV{Qgbe)MnbW#laMf4`G#PjHlIUp%<3ly2&o*d>RpmOTnmY2VHufF-SoA1<)E?~R( z=WgS$I7Euy4Rm(-QH_=+`sBw1ta=csoM*|uG8xBOE~wUwTAd@51j zuy`QZW4sK^2*CTH5tN8z;Mj{$CxYdT<=Hw1#U3GNO1s#SIAVG`KswTTkWM*}C5vDY4%wW!qp-T+P zjiH`H`Pj08wXN8~6_I0Gp}9bcbE~-^4mD3Jt=O_gbB3QV zH@0hfXH~q;wCr?tu*vs1?)CViBPBqx&5q{6GO8C#^wH0-chR_FWDrbUXgQ%zxOyH_!jd8*jbwmGetZ z>mI90oWQ{QRn`etwI7z}UM6U%>aS8Ge=hn7*WU)BCt>J`RFVl82?Fd<+Sqyf4cQeRYe?3g$5AO038R??pu*~f{I-;y@--*Usl#4Re< zL0XHkkYPBDUr**?V_4F#Mn-@8g*jJTGHZ?Tt9?CpKKr#hdN1F8-^loVTRu^_1Pm+j5TO#%nF7n|JOqvwP95V~0xY6*TP0JMx!rzqf3C;CtWMZ5^~0 zfB$CDI*O00kSYqexd!cwb5wk$FblTdB4HV028U~%vtf*Q%f;rdIV3Y`GsSf4V#7cw zCfk?Lv4)H$nsHSE3V9aY)Liqi7Y81?fbh=cWVC3e2(E;^A(2-yY~Y<$WZLA)Y7gE$ zT8E=mZQ+p1K(^Syah8q-KrYPTrn>-c$%9<8=VNnP74)pTvUR)I5b;omxX3DD3l3;dW|5Dauo)5oQzd4%ke=n%?~M z83VJpFzJdbi5`Mmay@YZ(+%OsARvLo1SC=ifx8=s3|(X#g#d^XKyO?vL1Z#q?Zb;5 zA-fy+dO>$`EsG3s{LwJd8U9DwWodXXebC_2=_AG&D82jX5Lrq30g|WU3-n9;qCyE< z1?eqPcW{p*(2a2s325o|LSc9|Aw45lHu+UfTu(L|)=yFP*VE`$m9;=Po8=Y}R!}aM z;WRW529hmKs7+7^%Bl}03PuiYIM^lC*n;I+XCVHGG6`wTL(U9~xvx*FgS6)E49qQ% zC;{JnAPtIzXtlv-0G~aTPufS%E41M&N2w&e_2F_XBhp*Ps!L~{dD73yyf)TNi=pdT zNP@zwBc%)LA(R5GyG`y`07Vhif3$W;Z9geJw zgy{`K@NafEbUml^`&HpcBusC(FOTyw{RZ@<`_@2y18KsYLzqEybJdUOVAyuJKY9E# zy8nLMKS(N6XIC9}f=p~dGDqksgTh&9$ghkW;;y0tOrSfn>_uvl!!@Z%D(&MWjXlLx z7&NiNe`EN*;PWEA7v?n9Fnd|GPcWzL5Jg4N0^J9*27q z7YoDQg7}`yo;_9#7Azd&p?6FG5Qp_rgBBy82SCT5LYo66_9A;R95{9;5N0pvbL5-- zkqE^(jjVfQ!-e3bgNHXsw1b5N%MmuCoqMP$v;wgoMTy5;j9QS;YtRL7CxS8nfe{!6 zYy=iEL9Hy%fV~2X0 z#O3|xh#tG%Z}*6UDbZ(VN9;Z^B|7ZGd+js^n6tA>CGoYbTiF@3mVJ2J=j|?+o!-zl z880I~AS@(>cJRd&JQ@M$a&ty)hnfb@Dh49Udl4-cqa2@%X3*EDM@yqOtz|8Tu0$~m zYE7Tknnsu6jma2wNo#M$UbG=W7NHtfw2m$aG@p0Bqoy_kFC!^NMs$OLQFh2!z+Ix7 zM>z-tp#eb?{XvR;XdvZpTC?;Pp)|W?cP_uOrPRD)YKOzQ8=6vKS83O-lDU7Vzki5< zI&>8&P1d?OJ+0UY_@_0)6vj2XSd1>}KL?^m6nZ%CJqw$-0WX955Z4na7eyyYccvyX z2oy84(4K}4Hj~9e7zP9&q!4U^wJrfm(Z$@1`9i)Pc3E?Oqwg$s=L%125BqXMlQ&{E z>$jY(Us+x6Y;n8Ureeo6gTdamKflqw7Liabz7AKF^yV>dXPvVae))f8uY5-TK6nmu zLi#@DYYY})m#|SN#)#+QW#bcJM;M=$vf9P1p(+nJjE@pf*Lay0t2mY|j1H`cWbB{< zX62)l?7%1mF)+<>Y}EIuEedwkE&~6dBlb|JM0baj?lBR1Nh1-F@yQZtvKvTG?J+hI z&{0KOurbPhb=|i^@dk$zgzj$L^7yjSm)G5T(>afPdhw-uA6jS0HA&OzL*Xj7Wgb&M zlRrD(WVJ}n+-Y0puDW+gX~U{BZY$ilWW@%sA>;t&rE~??y=UgvhIy`es<9(OlyR{j0uR*$h-@{gKz7%1**%k? zlOYRapLB|@$Dc5IS1`Kn&y01wBjCvqRq&F2I@d%%3V$1Q2;S z`7-d2?uP^NVzR_O+)wXPjNWMt!S-8xyPDp`A$lL)3)O{|74C5YGP5#~nRMds7vZ5&8wZ(r^v{u0f2-j0|9Z zip8kJTaaIQyx-V2iuPB)t&iCs->brSvZGsL<3W8K8wA7Ug?@;aj&AC2jc$%R`qBL| zdSvwOCdpe&d%pIK&4rQpkrkD3LrejN4lxDjC1MIN zbgOuL!KFODppd1J+?pdF&NUDdw~~%f^u#*JCbB^gHccU`=Qh4}PL3Uz9NF=4`(x0F z!4s2d^>O=SPR@_sBD`gcXa1h;e}L-8c74pSj2ky(lN<+{$Yqronrf}kB1{D$72{Sr zg21pec7W=O5Y$8JI+^Eu1%a_gQk46_CW(W;L$pl@_}KW$rQ}4Z&r>0#QMlBVns7F0E8Zllg+cxU*K5-Sf8k)>cByD zR+)FVvn&69**9`M`(WL{B4+Zf|eCMz5v#4M2e_>(&f1matzv>$xLYm+}2ysk)hGhn7C0 z(gTPkq8vJcwj0s41jbqohgBWoUbHHi+8U;|T7+t@X8;ywxom{_xz^qxr&GjB+{7?{ z?)snKaO2OeU$Eex`ugk*=bwFb>&zD)xMb4<4;6Q*3Y|V%e7a3;!|_hJy@6~o6q^?%_}agJ3LmN6ZCOp;R)DbTxD_!`^<3T^{|m{t6j{>eFWHUZf zm^jAN4w)_Frm6I$XQV5vUy8DTjRhK9CUnLm-m&`L$(?y3a^Z#NM#AhO{Xt9h{8?*e z^%*@{9vd3z(Stqc5R0b}Wx?3b;V$q0wde}vW?eScuf6D37=90||J(*bzj%*0#>V?H z=Jx0K8Tas8B2mIGC}KU1@v@<#`+~6f>6ol&u{eSF72$P?(XxpM!b9KMW(*efuT1XT z8dfLf@77nq#YUqP(nh*8r}Q=I(+>R)bpG_uk`0L$)=UkOZjMm&65nC&!Fq&!W5aTZ zcq>1=B5*_zBuv5hn#YexXy!64NHIZGAxJb)(FDv#0PQS*H3Cr^_^>gcu0V`%0IMLy zE3x$VIT~8}zWy5U&60Q~YkJu@^0NMG{lLqJ@4%HW6O9e~_IA+N2Pzw0K?h<+AR-Lf zqCJHCVQm}rU?7eIF)rlQz#;T}S| zkDDU0&~e-a63FN^N1Ke`+yL%j{4?%Uxe?v!#GC0gl^a%%-joSNhi=Hx(eq+U;+S&`Fa@@1PE$UPzM*eQ7r>_r@;&9^T|8jHMYXl7SkT z#`hU~qhNt%N5t;oAIpoW!<3=I-ZFS}+!*19z=J>_5q4xuktJ1&?ts^Gq?H}xCMWxbjzPlxD9Qk_L>0cH`(Z+GzVq^oEQf(Ocfzf3 zl6xVHWb97-J`?UiV^o0OOO>0rPUEfUG^EgwDnsl%$$mrV$^zP~Z z#$5T9V3GbNe~riJGKAiyza=jJi~b1P@E39Iu=*Fa0bA5J&+%W#E97g)nn~JNo`oy{ z9Aq2xNB$~K53phNMSkhAfCbt0{@yiFB-)gTmsV4PVs3&S0q9$Ks$mZp(2I6rax6k$S}jQBXCO;9WV$4Id%HV>U6FP06B+x-ED9c3}wu1qy@_{Yz3EU8f7CQ}8fUNcbR4E(RO5=;LRnx%r@Mm`?QTUg1HYU^S40y) zeeE|*g(uehGat~j*M|NAxqDi#LF4-sfg4U49oeo#ClF8fN zP@m|U-Bp)8eNO5wta21vH;!M$8qw^uTTBw-i#gC)&9mpp#UG zqN%=_@C`&|TOw(~H@Yy6KBy4;8WJ5DK73y6A*M_dC@d%3r!u7&X=>)ShtiWn`~@5t z5ix`gxR?cATtL`4sN*==n}>fEyEuqbxxn|McYeCmyJeI2M?b20eqHG^cSY7$U$Llk zfA=e;nvDxfi!QJJIefP_-CtWO`ImokPU(WZ@t0nzd*G%8msS7dC!Jp^Exe@q$3F^P zI=^J_>-bpD=vd5GC2r0Lr8h!5AzEl&li^1(Q#|I&Po9548x4-*aRC!KaWu+rT-3v< zLcbQ=dFN##|2d0|#&wPl-~6|cOK>fpbL0C^b3z}+ho@HhK#{0peK6wI#`<75H^)na zu|7atu~W5v(~h-2-l;!+%7*KS9c#-w^(Rhfb6us)V0^GYF}{%;YOFXEuL!#Hie*!VMmqEGUdkz?-?<3F`puEwF^~KXmeY~n!P2F|69iS2 zekIN>VohjEi$2q68Bc%4?+C)ba@`v6Ne_%^YPw4@&%OIU9;W`EtA2G`>GoHjxzNho zMlZz1*`F9MYs`pmQ4DR7sjiIXuIP9nhJQZ1lz8YimfESme%sqSS?V@@Gb+MV4oEgS zf?de21|cEuly`zIXbBA6xB^>O;lI+r(sYsj8ryptOYhWQyG_Lree*W`HL-_&EWJa2 zZ5t%B5mWgfbT-O8UBc8-Z!+zF*_u-cy!@&^T?ofd-v&S6{ieKMbjhfdVCfC!dz0YTeul6S!&fa^ zer>Z#fhirCi#LAZ?zb*#TX@lxpSzRJ*dE2Hs+EI#Q!~%Kbye1HGlgq%SI1&6 zVfr$}6FBAB@_zs;Ng#@C0oP*Zl+`&NZ90ZxAzstxfPJR+LP>*A^CLw+6f_zeVL<4h z%S4b|m+zPJy<$2T3Z~)n74y(=B9cqCm}#3`VY1Dg8y%cFrO6$0`IoIxOwpj-=9VO@ ztELg9A2!VzaHk&oYA}$V=k_jJY06c#T)42qEjnc@V-8QPH#Ie6adppR-x`cexurc| zPxjA<48EIQzPAux(B|{U+##!j$!353j9Hh@dYY}gtZnrpCX}G~)NA)!qZeHE#7gJ1 zy6(EBP>n~ncPv>G>$n^u=lJ)9o8))p98j>Ch+Uf{P=pNMft$_1P^~FPmF$uAO|~A$NM^was_1 ze0XYKq)Yu@wc~<2x-Pyrx!C6yhnnn7YgetGm&wdqziKUZChyzV&p2mFYg6v5X&1TJ zg5;d3H4E2K%KPdCYp>oq>*DJ5jg2%-K??!2P=Q5KM8j#qmxZF6W-3{tgBgkjReNi{ zJ>x(B^EX1E)vmfbT&nZCCe6kE=2EM^i}>z+4!6_Sy3fPkYxsLDe{baPNqR5hER~W; zm|>tHUK%md$oN9qW1s5i6P|ZCt2{NejmeJ69~-dakjp*cU`K~KP|LuJL~9D4&ang$ zIPWF0RtP*3G6JC=xB?kq`G`mZB99V${*39#&*?9JF1h0It1eF4ANs}f$xZigqGm#o zscsi*N(I|94V}IW+t8Yxbz4VOZLKAF#>UT%kz3jM;qrR|8!xU++Bw{-!2p_onm6Fp-Xb3Bu9Kb9%gx6GDo^8fi4y zLY6et=YUcNDC>&4q{)@63k=`vpW+|B`M=nA*mv|N$l)`4_Pm%JYcRz=JXjEaIoyt5 zH)PR3dnS=f@mc|_gDS>xzCgjF6dc`>QIlNGLa}jVi$NYG8LUPWL^4QG5R{{;wSv=w z2n*1{5wgi_5o`vNWY3V#H&5sT;T$Z&D5p4`RCsQ2h9xX!s==I`1f`xP(Kb*SxQ zN2Wpz<|LIBLexGyi#{H7W98)~s4&ZjaYmXOG*K+|4rQOE%FFX8Jh0MWV|R8T6d%|q zp`_q4nEHr*4jKDcAcy`+VHuAM@714T(hWPF)1ML_-*LkubnveLPKRD51ob6S*>2dm zfB62LHyQ_s-)M{|X2T0z)TpikG{i~H>2WC2ME4j&uuN(sT5R}f{bz_*V!J3H%!r>S zZk|Ro088`nPlB7G1+o7L}Y=BVO;jg9^4^pcHV{O%VwE=gCLp_f8W7KchluZ*2l<8b)v6HRR$)r$3K zsb$5@mt46#ms@`2B{#2NYlyP+BJ#20zZ1SGUnIRjT9bq{_B@OHo~>saemDHj?4jQi zT=si$7SVdH@VfkCnQK>Y6hN<>E6x@Nf2Tj9?~%g8-w|j1oI+2QQY`DNA63>7PL4(4JfOX|%*2>y`#BTc)D*1fwSL`O* zZ!IBiv`+scFGU0d9kr?c2sZ%Kd9)F*zKnD`XhCy@Vgrp=O-^kC?LEju;L*Y4d;v}c zHX+#r6{+!{3ez4Ti%0;Y>;ouETBsgvYv-eqLUE}$6ePk~31yXBVk_e-Djy-NtTUh! zVtJ*@;9g35O>X4W-kLJiDd!L}-1~}Xjd-KsmN25OTEba^VZ~7A@SU-Clk`-z*Y~Ir z!0}@<<*Fc`y; z50@i3geSZnq2yKRb|azH_-)K0#Q#!`hzDb3Al8`Z$a;jukBC&Flae7u9v4f1>_Qk8 zWA})I8!63k+?|e9Q*PPF)FPmPu@3OqHjIxAnh(#7<&~XaO2D*54JQMZlabJf34ts| z&ICDp?d6wQ3u}4#W&I#=IPor|g~7l0*$nK_ZTQW4o?S%ts6E3=LTRJnWZYd7Ckce$ z_R*ifPw^ksfA!K!L}DTcU%%XtdX!%Pf31_as22Df4|YL{5-1Mt@#8LV?bVH7cSwsM z*%0N$)S`&^gH+Dr%jE1agQ%)dRo7S zi|v9jWROy9wfOsBx;-@9$iwK-WC`&gMy##_vMLX&hgVgDR|hrM%pR=;ZOihsX{`m0 zMa_w@I#Of6vi)c#5)d_lx?HjrN_Ez+txl8@Ao+L*1WkzEb7!BSv|qtK`AvPCk9?C7zt zm-Kg>4ptvvr|Z9yR&ck(*YPc~hZlnW7l1!nQSGRwl0}4M3q-U=b0kx%v&Ci}Q{9}T zytwX+QF^F3hhDWIf*4|yTq1eoGv(pIrb%lt2Vgk(LZbjEW-A$TrU)6H=7xoJe(xt{ zx^GzNHGBQ%`0>8-2KUS@iodSbYmF2xd1Tp5f1NtjTg#qsPMJH!(RnF5ClG#y&0BJ_ zKjy0q_!^n-mL>YPoERrJ}@HYGXmgax&nlYmbhyp{dNo3 zAK-5MLkdvfPfHKAKlD)hp{0M`zyHr8+ke`}zJo)5+P9CNez@)M(m(Cr|EHyg+mNnI zYc!2HmifJCX8 zEEhm2LMf3Z=Vf8WR`=14{{x)g!Qk0xTV#6j7}4-7bu#hkr#i1wTB38ASx_d?BdDvT|Cv($dQ}e z_jca*Vml8TZl4b6LP>J%==^@CQs<|PAwjEaM3)nNYO|tN_i27$8O6}_(>S`E2Z}+y z{*>i$*Z|2-n(N#@@_4--J>_)@TxP%Z*5f)H(khK7Zm7zc#*d#G@PI^A%v zq#&91Tb%WBGpAjcXqTd>W5Ac1GzGL{Y2vERE)hb|WRL>13z<;nu2Nkh4JQi1-yy@} zc_nF~L^q4e)BmEUx@ z9X1dQS|A+fpfF7{2^sIuSxqijEWL;coF^3XG}oqJPEE_G0bmML&#c%SAiJx1D#(+= z0T1b=RL_ramu7OZc!9ZSE+kzdt_uRB4#}Y-{_k`W>_M?8=@j5EGh|s1h|+Y*4(O#x z6%3gaOPq4ZHt?p4RaK8R1@vc@?pl1kJL%dSJagsq!5X9G*(`Nxoo=%NP5r5Uzu6ak z+``rnX)alH`KHzSFIG8O)#X9Qn)|#}qcmbAg3^9Sgw$V0e0!|c0?{m(l6X+P?1NfvW;@SFFc>kFd6%d41Ub*|j8>e9|YV-*{2u+h0(4w($QcifKyoLxB9QCXMrgQiF=7vW{eSGiiVM!6{ z6T45pTwHy_Z}yzKM}LPL*zi^RnEjO(S&Fs1RPmubg*JJx>P@LwW|)EqxS=*-A|uoW zH7qEULGuHVq1sbH1r=-+66DBICqIV5v(%}oBvt$n3C@Ox4=uWW{GCheK57z>ecmA6 zV532g>94=|3h8wdY1Ch#k%E>OsnACB9a(CX=sSgsStne=WTlzlu2yZR7X&g9OYl~W z&D=?v1aH#WUfn*>e1{UcW zIL39L@k5E=2dYPLk|vT@1qSxyfqaY#{Epa%@+g0K5Y6*>;R~oBZ&=!Z(U)b^&t#bT z5Vv{_5jzAbVq_o2gz}T6i-8?d23#(a4?cnE3s+xv`yF?G4kA~z1J$f*NOev-}lMFTj~RP~}vfT;+LWIQ6D!#^cJg zIgN6r<`iMgxQ~k_e?FMSn?D%nkn%ZB((CywpfHYi_WaFSXKrB5V70Y+Rj|J=Z0(R* z+Re;#(I+Ae3CYz_<(jM5X2d!?S&s}rN*1j(wIQF+VfL7t>dek2m&+&1N!et#R0qu- zYt$RE*_#tHoeo>H*XgiiR=9m$cWZ6G)jh)<=$9nqEOjwSs+H`D!)s}IL!eMxu(76d}Ac2|qP#^&`&Hb*EOh*{F6D#;`_CW1~$a(c~n25MQ-Zb!({aOIWG zMvL94$knTvXqKJl()t8TQxM^&xC4<Z*{)9zOH75B7y#I+k=={;-X_P1_+_N=*?;io+w;OJ1Vh4qkqPjg=tRY)al z4mBoFSE9SD=DBqYCu(Pz41G)|=$BJaX#jvE=05yCJqNX}KAw}nYg!h2xb@aU)*IEj zB%csw{AAPZ<1z|>qsA$mhP+whjk;59!wN<88~6Mmck>5hhTgYMwh3GlKp^s{NrvE! zV^k8)*fR39DlS!Ipd$I%u&V`4pgL2OMn;PhiVq+a7J0A77D~74kCx=cKoqGW5EX#I z-ep22d?&WPkzyb01V2c-29718EjeO;7-w7xG4#60)2r z`z=AIs;LU0n5A`B&|Fw?)hHTeKq;h!8dx0+Q!?Gcq@o5WH$9+$ma;mnnT%tCGNv^n zkCPA$5RU(G!^^rLR&H} z*b8yumBjTpQrJ;xBW0NS{bjY^!~G`n%lq>4XIbI(*TJhqKP-iWPElO}yNj3A z(E1^Lwf5=IfATOLp0l}qa>j@{icp}nMQ|!4lWUZHE$!3$X|u@)!ch~7mO(*+&aP@U zR-tRG%1@AE_lUl3=;e3jM3}MM-F0X9Z5^j2^cyX6*!6y2s4nI9G!Fl!dqMsT zo5|hTn5y=(v$|(&>a7W#yTxib^VqOuj%b=SMe$s)Y|hF}XEe>z1$OYCm-Y?Rd%9X$ z+vr!%%dAzzctXF%GK+m8=m|BZ=@$oQCi({&8w2!v`5sw$=)8?*{_VJ6na+;S+JE-i zPc_E#)%Y>`6CsOxKKR zaZnY^tD5-2PsSIAqbN@SWP!6cjaArB%XlyZ(-xJQV7bCS&q=%drQ7d0@4|a-doi(g z*1VV2E1uS?<_^xAwKnnOjQ)Y(*&9||=^U8VzrJtb)Gb%#=1)Ig@_h28+irX5lO1PV zI&bd3d@>Z8dfVL7=FYqHjE=fBr}YQVxZgR1(`PA2!pKtW9@A&)jwemls zPF4=+jvo!d7&Bh<9-)k=fRAyunE43^6@;KdJpq_Zl~8Cb5r#RqWA>S653;(!!5vn| z#Rv2o|L0t9M>s!tU~q@UdGP^u2lg|Oa3VjrWAN;A2lPJ>Q-8e0y+*%}U?- z-*dg~Q}TmMJ{#Y%^KY$Jx^m&fC9OCzIH><|fZ8kZJZh>PNEKAV6bH{etq?r0su6Yv zM27McAdWCH*!LP$Uw8!#E^0Eo{7W5z6N_dOoIRuv16SbX+(xWo)LDpoE1CJF=@&fw zuD}j#NZ>M5a`F+9gY=0{o7OHg`^1jHrJ4B9wq=FXoE6hsrAMs2 z3kMpeFV8m>A1Zu)byLk=kJ93=x5zUV{Q1eD6---lzMCy$W*3U04&~3fbCzZ4GTGNQ z^Wwqzi>map%i?RBzOnz)Pdb(?Rn|6b5+mWZ>VVk-K*DRCHr(pHV_+U0fq=0r2p347 zLrnE7VTVAN7wiV8C=u>WM2UGHe;|mDKM=&{s?Zc}qCQ@OzA;;@=G70YBXAg7IR0g! zdKyTZN01chB1Fk*IFt5?QwC>|&~+=%Iij(at{m;SylNY0+kz!cYbWDUP_#BIa-<36 zh+d#2mnz7or{WTTiy=`c1T%GIsm!(@mzsRQ7gsSuAfF0rDwoYdw%5-$) zYp1O_r)j8oZTF)3aG`xpy=i z!Wf~#8(bv7Y(T?paY2HMR!0TqfmJwave|uJPXL+= zGUae1Z<#7>01QUQ%zdg=!I}W0my}vO3!_Q_PK5zAY;iw*C zohlD;OcH$sS%AAhasq&EIP`_6wq9=2aqGh&9$sNZCZkDtHF(7`g?{ zCQGZr-NefnGhMX`&@q&#^MjIqcu)iZhNtcW+Jx4_SB*$+FR!odrScx=lnZMk z`rsh!YM+mf4h2Q?CoZ86U}EZn!daO2!G|h7W@5TuDnLpQ{zS#t!_CMq&lG)zATyMnU8-xDl+#rz&r|`(V-H@X?Y4CZ)2I zys9li;xI@-NMHVd6wQH&wGX5>vRFn4jv2+>r~ES)7!fB(IHHyr<-52QTOm4mlEz;D z-`eXyd)>Uf5HJuvcD_#7z0_WN@MGGGif7~6JlbAr6R1ipKEk&Q9vN#YHJj)QNeD(+ z4Bt4#!nTa%?gCRFV+>{h$5x4Z$ruBAh`4yDC=(-2;9D7q531ykQ9|RR@4fpKN;f6X zJd#h1%tgZ89(&t3@%CwS)Hr9@lt49X0 z7DMjr$G6be&fa^J+Cn+8UwL;zBTHe^m3NJd+3_vaokx!n*$ltm2<`si_VNT@ zqrGVQ$G10BN9nwyEt=5Y0_w2x*1q>B5qx}W3+Tv_|J%0y!?cY{)Yg%4p4e7)gg4e8 zJa}a07!!bBml!;WTGflJlh6~AEpQ3AcHa4E@}@Ev7|o=zzC-d&a9+NW4xL08ie&h`Aa~I z5b*~+T_@y##U@O>-h40O`Wm2X z2^RBf))4D>$YiqFY%Zq*Ri|7wYe@ek`+_K1Y&N%DenJ0Wkw>)n^o9O_!|JXQFGlJ- zLt!_k+iCNdf2sd`jgR<|&t*=xYRqL+lLLctHO5Lg*_3L87!SmCKrB*dhcUIGPtk8@t`e8gva8;$9z=*K^)S_Vk-9~LQM9dJt2mhw#fJydT zbxkB1Yb31~`auGO4g$D&&T0er%#YS89Bms-iBDT#HxTMZeL&Pin&K6cJZqpbo0i@% zl2QHemW2i6#v{G*es<)3{Yir*&RcNf=SCRxhNW*mW@Bsa*PZw4k6=!X&&R0~&fqy- z=m%I6!EjiSNPRaoEYX_Ly3#z?1@6e_kzMI>19nEwP)r<{)$<6!N5rmj zVwUAdjt-o*yhPjy`7V{p@S&^rTy@o+$@wm$#o=`?oxWe4|G3Nhvzl@;WOgS z8vc++*v&}dvqE3sPp9(|fE?s20i0L}45L|P6JZxC6zt=2$kh(dv1&xszDS{sR4tQ= z%ew9QyHbp*5)+%CLKX4th#Vccf9s_CGcwvg_U6c@!9Sj#K6-aJe^^?d#Zc{TCI^>3L)$eK#};^5lU8(CAQC6Ma{B-xcb+k*q$x?=V9rbiGSl^#y(I zZt;$BH~*ggQ*qTp`rHSGr)Dd$SfpdxIA&Xom>`4lK;Ga$q`PC%207V-{MJFbbp<0B zB|9oTq@|<}fi|J>4cKsC!)EbY($V`5+|Pb8)&}X{&wF(Pf(^xg`cItEt4`LA5h_e> z2O?uZg^y_pB7gugJH|C->w)uLmFRANW2Em@_&_Wi*l>WojrM)+UGZBV{)vwVJx>tN zAx)TO<>a;|>~A7UmLxRu4QvLNSxduFx|#T-l;op*^#VJu8p*t;in;O~6BB zgF{MEDxDjlWkp*MH4@13G(-xxE*Ik2>7=bUq^RHFz)^5~DdOKfJR9-Mu!IY{rMLVM zE(DK#9i3{NS>gX zAp(nzkWt`eT%!WW?&VENB9|}3s5EY+Vfs7Q-K>9#S~lm#>)3`H_2l94Eqq;n_qtoq zKn*9?--v*XCoAy>!1+xs(2}0pmjFdaYGW9UL3-3As#wyPl@*%!;Bny22k>d785cf@ zbhYOz1S&lFD9o#Q8jc*kK%$I3rWQSt%9-ULU@es>@j)Ovv6^c{V2vNLV|g4$ zXL=wf^|IoHCNp$|&YN{7?;a!$6zOR_q5{Bq<-UsgOM?B`Z!MU8y zj`jliV55DYnh1*_*N9Ul=MGS0333MFpb}N#`*69e8WjX#fgk0u!zl{xN5w!d|3UJB zB4SehI`l!Z0gcMow~?np3)TXg5E1%O4|@+Onhwc)6+xC z7FJ=ELh(_N9+Z^lW==8H^Uv41Iqd*an* zlYTYr$}6HiQMbY6R`@AVrtgcT|ra4gKTFlLn zVAm!Jb~VSyD#GKBNO|K=J3_)qLx)5&Zzfsk+;K{)AZYEqU=+2r&`sR@%Q=BQbUEh*&PMN|?wt!2zE?C3FDLAZeVcSO!AG?bVgX{2D zv5~70fgOXL+=2M}A}T8LBD2t22{Y%ZK3+e;K$(nD_{dB3fMltLYW$C=)MGVP5L1^+ zQoZI;8$KQi;DI)Afd4&7)cYmxFSOGGaQR|#T?}1jZ2>{2hDDF@Kmum^Vt$MiD&uOy zph4Z^^YnwbvSRY@DxG&;sW3eED|dVac8o{x$dAa6peKSCP;ldiOmCF1YZ%8FBWg zx5IUpOIEgQJhpR-(&c~AXI361(s8?l^8u}InM!>nh-LVJDQ@qyj5bK?m=kKR7Q^$& z)Fx$LsyREriAJFbdAO7MB|J|DwV*2bQKZv@k>L_!Ggxmdgy1!}rVzf?A*1Yr>}CN3 zB#Ob*ip?uhsD8pOb3xpExZfWM`+w*U?_m8q_=dT*u=Vwu&wBh5g_&(OTlRoI=VFB%wwdS<0=0LouDekb3&R@zi zs2TOYQ||Y;%Ds42M?6jCY~jloeJP;;J-y?&^o^S!BSxyu<9R?d?EDX|{tD&*cmJqt zCHu*ECb}P9eynULRZD0xP&&Slas7bi(8xpZ#!B4eFmWgVA)tUs5KTZCLi_`91$>8d z9v;F#pOoi7pTo0hJWcd0Dc%Osn4|pJz4I$rjiEP_-Ge}sQLKji@j#9c;;Si?KkX01 z5=|{!wgM-`er+t(L{X}U*dJAE4ZDq8ZAd;&AU_$3Rv=-5s3ol12LV@5w~8-NzUA=j zttzja#2KDyQGsqmNbIvCbcOE3J7sI^HG~+6;xJ=;;NcJ(4GkQ603k*(Zz;9_cc9geb$EMrfZuz#kq7AcODK)>DIO4|cL z{v4!JwB4it20Uqt(WVodsz17$4)3N?f0O0`)f`I$128a4%mWyX@CzlfRH8A-AN5l~ z1R(ZC+fMV;i1?@6tT<}Ud&mt$_yL~VP?<% z+}oGh29Ig;wr!~shk*M*R&86eX4@(%nKgNiCwRW=Xx}P5LEh_VPbzIi_S)zik0YFd z^rw+I-jHhg2rim1$LTSKm=h=Ii@`(S`FjiGJpj=C5i^|dZ`6_rDyl;ri^DVhcO9nF+`LLxhAJT@1m+zLeY z0h>b<2zo@Y$|ypIb#oMcOfCn5)R7)849424EK9m(yLIYAoY6@u{RUf?;(p=x9tP@vctQN~Bnjo_K^ z5r()@gjJp!RHq1!tDzN~l%m3^N%I9VSd2gDpU2-n{;>R_d>U4gm~a)3a03SJ^{7=8 zsRBnLWqE^CkY$FMMTK;YdS&op6Ziwh*JQ+c7Xu-x*RMrLRrSI^(Hw9*Xl`^+;14?8 zC)karE>|h2*$^;m@ZQ5eXCb}=Mw;U9Bdx$F(L>(=X@eDb=EwzlUk z|NO7T!PRUk`iSv=Z~6ae?P`Ofy3X)@*98F)Q4tXo*AGDD!+rOA0f{J5gTzwXM6lK% zB7zDS!4DdnrY5n}8f(?0CK^qnX%nj!t+B*9Hcf2DwvOo}*0lNPbexRikBsd&X{Y04 zpwGGYS;fSD{K)Q}ecyBLInQ~|-RIuD_uO;dv)26Q9KCTQW$A`@o*9#zva0VXlVYx1 zZnw?!`Ddd?2HpDEm(7w+#(&i~I2kxGJkzWXgRU9djznBB+k?mknBfebfE5X{Uv@3& zy3-6CappF{*s;H_HS@W~jYmIYiTTfP*0QN~x8nZ70>KC4LKk!5#g9%|@tYenS%TZL zz8ig4;uf3l+66*~-Fxw$gAr%xqs`0|JU+pso4nyrFy<%EZUct4 znC^TGRmWb9?}|=$w^T(6Of5yBs+L4w$-{M-yOwkwbfqL#wYbg%Ye%J~SG8pKT`VjV zUv^7X#&}QDj75*d*FAKw(>=`XYB6mvq5Q@E8`~ZnR{9TXJnqKvdNVl@^LicGU);Yh z?gPxiF<#{DdmCsd7njlhxcyz+_jcR|Hj*h4dmWHoYl=Y|5HP#ZiMzI$lK43(1$WC* ziK2gIIEc78&gVMPY(rU7-X75G?!hQM8w;MI9Zb_tHyQzX`g@&lN8K?y#v#v2<~8|Q z#>#Zc8jrGeJ#Jv^gKo;1G{kM)$bsczcE#}TCS#cBCAwu(5ISr%-ZcAPft)a4+W?II zy+}9ZV`;k?UpF8vwk?L=jcrDc1#UO3}Nd`0|~!PSF%2473qo#;)hPu!i9lvI(_opgQ314DKUxtd&-+%t6S(Dg$Prxd5u zr)*7mf7qW=t5dsEFAq-{o;!T^h_n&)Bi0Cz(~5n=(&jUe5e5D=o{LH9u=h)~T$&W_>(1W$dD{hsItX=NtEW zc53$4?2pD*j(>jqYvZqY;yu$mm7X@w4$qAVD<_$T2?zOy>yp?$ur$nYSPU)Q*ntEwk+q94JoAXcP-z=yo*i(46@M=+0 z(axfq(~G?s-cy>ZkLX*z1YfVe-oGP|8F(S+4mJhPhSEceLnp&Y;rj5A@F$U)$jN9% zv^M&5^ipv~@si>##g|J8N;*saQaZD=x%B-R6*FEcOD&sQcBbt5J>Gkso#~ocKl5by z#PaU)zt7q{>tD0GXaBRJw4%OZzkT+457(5oj~MVo5a6gm;NSqisd){vPV*c$()gsn z6_>d2*w9*un4=4xl5e8!Lci@H>VwR+H+4692K%VTSsNupJ>Ck*G3p6cx_n4I5&BK) zL#)ZJRO-pl1Jp-Cucdz8N_WL<_^su2?cA_oL(z)WU2B?KmbJHa6fJ9S#i-48%-Qb3 zl|c*E^=!5}ah32gg3t0|#H=4$1GaiFbAPGT200J;*F!h?SD`1+1Me}b@ix~MF@z2~ zw%qE#>Q!rzdpVAVBFt8;#tH;AIE&wlTEA$`hi@GZVoOoF384k}D^O+u@~?mg`_*hqO74pFS){^GVg0`rcs^C`0lOU?u&~|U2Lo-Yv0LF-c-zuuGv-f|u^6tOX-BUMM z=3RvSy&Avr8vOn(w7LVS#{O12$LEn}AzIvk_L_ZSSmx}L`|S8_e)+JEJlIPSJOeNc zEXKYFAjRQh07s(z!pdFtBU2|f;QKusr!FxbXop%U7$*`Z@o;{XAc>MBLj==};nL6a z?GBd_*55FxH4UAr>3BexA!8&{vSch~`hOUa69KQZ4t% ze2lxUkuS*t`LcXP?uWykg;FbZvPixvi{)#wL>@FAdZa;?p-X?cG|37$rfiXwvPxD< ztF%eGtdWOgt#nAItdsS!K{iU4d|e)vP4W$SM7}AH%C}^*Jcj?2CuEC!Te{^tvQ@q- z+vG{vF5g3U)b}w^c$e&!r{rn*f$WiIn=9Fe1POnxdoavaldekLd772JvZTzchIIW51CGZ^)7R(>h3$*<&fc|*?0ujMyb z+zv~>%J1a&asge!7v)X)16Cq zNZSZVyK+doa!9*!NV{@K8)uGJ?Z!ab_>ja=;;7viq!Ukxr^Hj@De-*7^AXQSJRk9V z#Pbo)M?4?#e8lq+&rdu*@%+T|6VFdPKk@v;^ApccJU{UQ#0wBFK)e9)0>ldtFF?Ei z@dCsP5HCo)An}643lc9#ydd#{#0wHHNW38NLc|LZCq$eOaYDoi5hp~P5OG4p2@@ww zyTZf^6E94>F!92~3llF)yfE=1#ETFwLc9p^BE*XjFG9Qs@gl^F5HCu+DDk4iixMwN zyeRRa#EUw3O5Q7ZujIXYopMV4EBUYFzmoq-{ww*ftO8zVPujIdy|4RNV`LE=^ zlK)EnEBUYFzmoq-{ww*ftO8zVPujIdy|4RNV`Hv+t&3R&ulK)EnEBUYFzmoq- z{ww*ftO8zVPujIXw_e$O?d9UO>y#F|MkoQX7D|xTvy^{Az-Ya>pA%_o2{ww*f ztO8zVPujIdy|4RNV`LE=^lK)EnV@(LhUh-eben*C^B33F^`zzF+C&yytvzO0{|1%B6xsj) literal 0 HcmV?d00001 diff --git a/dist/fonts/glyphicons-halflings-regular.woff b/dist/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..8c54182aa5d4d1ab3c9171976b615c1dcb1dc187 GIT binary patch literal 23320 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 literal 0 HcmV?d00001 diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..ee2da91 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,75 @@ + + + + + + Puppet Explorer + + + + + + + + + + + + + + + + + + + +

    +
    + + +
    + + + + + + +
    + + diff --git a/dist/lib/moment/moment.js b/dist/lib/moment/moment.js new file mode 100644 index 0000000..a9947fb --- /dev/null +++ b/dist/lib/moment/moment.js @@ -0,0 +1,4463 @@ +//! moment.js +//! version : 2.18.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, (function () { 'use strict'; + +var hookCallback; + +function hooks () { + return hookCallback.apply(null, arguments); +} + +// This is done to register the method called with moment() +// without creating circular dependencies. +function setHookCallback (callback) { + hookCallback = callback; +} + +function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; +} + +function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; +} + +function isObjectEmpty(obj) { + var k; + for (k in obj) { + // even if its not own property I'd still call it non-empty + return false; + } + return true; +} + +function isUndefined(input) { + return input === void 0; +} + +function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; +} + +function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; +} + +function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; +} + +function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); +} + +function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; +} + +function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); +} + +function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null, + rfc2822 : false, + weekdayMismatch : false + }; +} + +function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; +} + +var some; +if (Array.prototype.some) { + some = Array.prototype.some; +} else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; +} + +var some$1 = some; + +function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some$1.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; +} + +function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; +} + +// Plugins that add properties should also add the key here (null value), +// so we can properly clone ourselves. +var momentProperties = hooks.momentProperties = []; + +function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; +} + +var updateInProgress = false; + +// Moment prototype object +function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } +} + +function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); +} + +function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } +} + +function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; +} + +// compare two arrays, return the number of differences +function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; +} + +function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } +} + +function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); +} + +var deprecations = {}; + +function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } +} + +hooks.suppressDeprecationWarnings = false; +hooks.deprecationHandler = null; + +function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; +} + +function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + (/\d{1,2}/).source); +} + +function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; +} + +function Locale(config) { + if (config != null) { + this.set(config); + } +} + +var keys; + +if (Object.keys) { + keys = Object.keys; +} else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; +} + +var keys$1 = keys; + +var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' +}; + +function calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; +} + +var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' +}; + +function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + + return this._longDateFormat[key]; +} + +var defaultInvalidDate = 'Invalid date'; + +function invalidDate () { + return this._invalidDate; +} + +var defaultOrdinal = '%d'; +var defaultDayOfMonthOrdinalParse = /\d{1,2}/; + +function ordinal (number) { + return this._ordinal.replace('%d', number); +} + +var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' +}; + +function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); +} + +function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); +} + +var aliases = {}; + +function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; +} + +function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; +} + +function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; +} + +var priorities = {}; + +function addUnitPriority(unit, priority) { + priorities[unit] = priority; +} + +function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; +} + +function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; +} + +function get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; +} + +function set$1 (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } +} + +// MOMENTS + +function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; +} + + +function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; +} + +function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; +} + +var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; + +var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + +var formatFunctions = {}; + +var formatTokenFunctions = {}; + +// token: 'M' +// padded: ['MM', 2] +// ordinal: 'Mo' +// callback: function () { this.month() + 1 } +function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } +} + +function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); +} + +function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; + } + return output; + }; +} + +// format date using native date object +function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); +} + +function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; +} + +var match1 = /\d/; // 0 - 9 +var match2 = /\d\d/; // 00 - 99 +var match3 = /\d{3}/; // 000 - 999 +var match4 = /\d{4}/; // 0000 - 9999 +var match6 = /[+-]?\d{6}/; // -999999 - 999999 +var match1to2 = /\d\d?/; // 0 - 99 +var match3to4 = /\d\d\d\d?/; // 999 - 9999 +var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 +var match1to3 = /\d{1,3}/; // 0 - 999 +var match1to4 = /\d{1,4}/; // 0 - 9999 +var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + +var matchUnsigned = /\d+/; // 0 - inf +var matchSigned = /[+-]?\d+/; // -inf - inf + +var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z +var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + +var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + +// any word (or two) characters or numbers including two/three word month in arabic. +// includes scottish gaelic two word and hyphenated months +var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + +var regexes = {}; + +function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; +} + +function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); +} + +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); +} + +function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} + +var tokens = {}; + +function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } +} + +function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); +} + +function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } +} + +var YEAR = 0; +var MONTH = 1; +var DATE = 2; +var HOUR = 3; +var MINUTE = 4; +var SECOND = 5; +var MILLISECOND = 6; +var WEEK = 7; +var WEEKDAY = 8; + +var indexOf; + +if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; +} else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; +} + +var indexOf$1 = indexOf; + +function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); +} + +// FORMATTING + +addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; +}); + +addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); +}); + +addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); +}); + +// ALIASES + +addUnitAlias('month', 'M'); + +// PRIORITY + +addUnitPriority('month', 8); + +// PARSING + +addRegexToken('M', match1to2); +addRegexToken('MM', match1to2, match2); +addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); +}); +addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); +}); + +addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; +}); + +addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } +}); + +// LOCALES + +var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; +var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); +function localeMonths (m, format) { + if (!m) { + return isArray(this._months) ? this._months : + this._months['standalone']; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; +} + +var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); +function localeMonthsShort (m, format) { + if (!m) { + return isArray(this._monthsShort) ? this._monthsShort : + this._monthsShort['standalone']; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; +} + +function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } +} + +function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } +} + +// MOMENTS + +function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; +} + +function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } +} + +function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); +} + +var defaultMonthsShortRegex = matchWord; +function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } +} + +var defaultMonthsRegex = matchWord; +function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } +} + +function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); +} + +// FORMATTING + +addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; +}); + +addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; +}); + +addFormatToken(0, ['YYYY', 4], 0, 'year'); +addFormatToken(0, ['YYYYY', 5], 0, 'year'); +addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + +// ALIASES + +addUnitAlias('year', 'y'); + +// PRIORITIES + +addUnitPriority('year', 1); + +// PARSING + +addRegexToken('Y', matchSigned); +addRegexToken('YY', match1to2, match2); +addRegexToken('YYYY', match1to4, match4); +addRegexToken('YYYYY', match1to6, match6); +addRegexToken('YYYYYY', match1to6, match6); + +addParseToken(['YYYYY', 'YYYYYY'], YEAR); +addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); +}); +addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); +}); +addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); +}); + +// HELPERS + +function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; +} + +function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} + +// HOOKS + +hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); +}; + +// MOMENTS + +var getSetYear = makeGetSet('FullYear', true); + +function getIsLeapYear () { + return isLeapYear(this.year()); +} + +function createDate (y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date = new Date(y, m, d, h, M, s, ms); + + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { + date.setFullYear(y); + } + return date; +} + +function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + return date; +} + +// start-of-first-week - start-of-year +function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; +} + +// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear + }; +} + +function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; +} + +function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; +} + +// FORMATTING + +addFormatToken('w', ['ww', 2], 'wo', 'week'); +addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + +// ALIASES + +addUnitAlias('week', 'w'); +addUnitAlias('isoWeek', 'W'); + +// PRIORITIES + +addUnitPriority('week', 5); +addUnitPriority('isoWeek', 5); + +// PARSING + +addRegexToken('w', match1to2); +addRegexToken('ww', match1to2, match2); +addRegexToken('W', match1to2); +addRegexToken('WW', match1to2, match2); + +addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); +}); + +// HELPERS + +// LOCALES + +function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; +} + +var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. +}; + +function localeFirstDayOfWeek () { + return this._week.dow; +} + +function localeFirstDayOfYear () { + return this._week.doy; +} + +// MOMENTS + +function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); +} + +function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); +} + +// FORMATTING + +addFormatToken('d', 0, 'do', 'day'); + +addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); +}); + +addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); +}); + +addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); +}); + +addFormatToken('e', 0, 0, 'weekday'); +addFormatToken('E', 0, 0, 'isoWeekday'); + +// ALIASES + +addUnitAlias('day', 'd'); +addUnitAlias('weekday', 'e'); +addUnitAlias('isoWeekday', 'E'); + +// PRIORITY +addUnitPriority('day', 11); +addUnitPriority('weekday', 11); +addUnitPriority('isoWeekday', 11); + +// PARSING + +addRegexToken('d', match1to2); +addRegexToken('e', match1to2); +addRegexToken('E', match1to2); +addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); +}); +addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); +}); +addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); +}); + +addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } +}); + +addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); +}); + +// HELPERS + +function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; +} + +function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; +} + +// LOCALES + +var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); +function localeWeekdays (m, format) { + if (!m) { + return isArray(this._weekdays) ? this._weekdays : + this._weekdays['standalone']; + } + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; +} + +var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); +function localeWeekdaysShort (m) { + return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; +} + +var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); +function localeWeekdaysMin (m) { + return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; +} + +function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } +} + +function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } +} + +// MOMENTS + +function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } +} + +function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); +} + +function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } +} + +var defaultWeekdaysRegex = matchWord; +function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } +} + +var defaultWeekdaysShortRegex = matchWord; +function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } +} + +var defaultWeekdaysMinRegex = matchWord; +function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } +} + + +function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); +} + +// FORMATTING + +function hFormat() { + return this.hours() % 12 || 12; +} + +function kFormat() { + return this.hours() || 24; +} + +addFormatToken('H', ['HH', 2], 0, 'hour'); +addFormatToken('h', ['hh', 2], 0, hFormat); +addFormatToken('k', ['kk', 2], 0, kFormat); + +addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); +}); + +addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); + +addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); +}); + +addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); + +function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); +} + +meridiem('a', true); +meridiem('A', false); + +// ALIASES + +addUnitAlias('hour', 'h'); + +// PRIORITY +addUnitPriority('hour', 13); + +// PARSING + +function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; +} + +addRegexToken('a', matchMeridiem); +addRegexToken('A', matchMeridiem); +addRegexToken('H', match1to2); +addRegexToken('h', match1to2); +addRegexToken('k', match1to2); +addRegexToken('HH', match1to2, match2); +addRegexToken('hh', match1to2, match2); +addRegexToken('kk', match1to2, match2); + +addRegexToken('hmm', match3to4); +addRegexToken('hmmss', match5to6); +addRegexToken('Hmm', match3to4); +addRegexToken('Hmmss', match5to6); + +addParseToken(['H', 'HH'], HOUR); +addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; +}); +addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; +}); +addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); +}); +addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); +}); + +// LOCALES + +function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); +} + +var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; +function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } +} + + +// MOMENTS + +// Setting the hour should keep the time, because the user explicitly +// specified which hour he wants. So trying to maintain the same hour (in +// a new timezone) makes sense. Adding/subtracting hours does not follow +// this rule. +var getSetHour = makeGetSet('Hours', true); + +// months +// week +// weekdays +// meridiem +var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse +}; + +// internal storage for locale config files +var locales = {}; +var localeFamilies = {}; +var globalLocale; + +function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; +} + +// pick the locale from the array +// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each +// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root +function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; +} + +function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; +} + +// This function will load locale and then set the global locale. If +// no arguments are passed in, it will simply return the current global +// locale key. +function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; +} + +function defineLocale (name, config) { + if (config !== null) { + var parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } +} + +function updateLocale(name, config) { + if (config != null) { + var locale, parentConfig = baseConfig; + // MERGE + if (locales[name] != null) { + parentConfig = locales[name]._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; +} + +// returns locale data +function getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); +} + +function listLocales() { + return keys$1(locales); +} + +function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; +} + +// iso 8601 regex +// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) +var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; +var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + +var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + +var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] +]; + +// iso time formats and regexes +var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] +]; + +var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + +// date from iso format +function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } +} + +// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 +var basicRfcRegex = /^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/; + +// date and time from ref 2822 format +function configFromRFC2822(config) { + var string, match, dayFormat, + dateFormat, timeFormat, tzFormat; + var timezones = { + ' GMT': ' +0000', + ' EDT': ' -0400', + ' EST': ' -0500', + ' CDT': ' -0500', + ' CST': ' -0600', + ' MDT': ' -0600', + ' MST': ' -0700', + ' PDT': ' -0700', + ' PST': ' -0800' + }; + var military = 'YXWVUTSRQPONZABCDEFGHIKLM'; + var timezone, timezoneIndex; + + string = config._i + .replace(/\([^\)]*\)|[\n\t]/g, ' ') // Remove comments and folding whitespace + .replace(/(\s\s+)/g, ' ') // Replace multiple-spaces with a single space + .replace(/^\s|\s$/g, ''); // Remove leading and trailing spaces + match = basicRfcRegex.exec(string); + + if (match) { + dayFormat = match[1] ? 'ddd' + ((match[1].length === 5) ? ', ' : ' ') : ''; + dateFormat = 'D MMM ' + ((match[2].length > 10) ? 'YYYY ' : 'YY '); + timeFormat = 'HH:mm' + (match[4] ? ':ss' : ''); + + // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. + if (match[1]) { // day of week given + var momentDate = new Date(match[2]); + var momentDay = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][momentDate.getDay()]; + + if (match[1].substr(0,3) !== momentDay) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return; + } + } + + switch (match[5].length) { + case 2: // military + if (timezoneIndex === 0) { + timezone = ' +0000'; + } else { + timezoneIndex = military.indexOf(match[5][1].toUpperCase()) - 12; + timezone = ((timezoneIndex < 0) ? ' -' : ' +') + + (('' + timezoneIndex).replace(/^-?/, '0')).match(/..$/)[0] + '00'; + } + break; + case 4: // Zone + timezone = timezones[match[5]]; + break; + default: // UT or +/-9999 + timezone = timezones[' GMT']; + } + match[5] = timezone; + config._i = match.splice(1).join(''); + tzFormat = ' ZZ'; + config._f = dayFormat + dateFormat + timeFormat + tzFormat; + configFromStringAndFormat(config); + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } +} + +// date from iso format or fallback +function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); +} + +hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } +); + +// Pick the first defined of two or three arguments. +function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; +} + +function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; +} + +// convert an array to a date. +// the array should mirror the parameters below +// note: all values past the year are optional and will default to the lowest possible value. +// [year, month, day , hour, minute, second, millisecond] +function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } +} + +function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + var curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to begining of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } +} + +// constant that refers to the ISO standard +hooks.ISO_8601 = function () {}; + +// constant that refers to the RFC 2822 form +hooks.RFC_2822 = function () {}; + +// date from string and format string +function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + configFromArray(config); + checkOverflow(config); +} + + +function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } +} + +// date from string and array of format strings +function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); +} + +function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); +} + +function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; +} + +function prepareConfig (config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; +} + +function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } +} + +function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); +} + +function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); +} + +var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } +); + +var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } +); + +// Pick a moment m from moments so that m[fn](other) is true for all +// other. This relies on the function fn to be transitive. +// +// moments should either be an array of moment objects or an array, whose +// first element is an array of moment objects. +function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; +} + +// TODO: Use [].sort instead? +function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); +} + +function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); +} + +var now = function () { + return Date.now ? Date.now() : +(new Date()); +}; + +var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; + +function isDurationValid(m) { + for (var key in m) { + if (!(ordering.indexOf(key) !== -1 && (m[key] == null || !isNaN(m[key])))) { + return false; + } + } + + var unitHasDecimal = false; + for (var i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; +} + +function isValid$1() { + return this._isValid; +} + +function createInvalid$1() { + return createDuration(NaN); +} + +function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); +} + +function isDuration (obj) { + return obj instanceof Duration; +} + +function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } +} + +// FORMATTING + +function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); +} + +offset('Z', ':'); +offset('ZZ', ''); + +// PARSING + +addRegexToken('Z', matchShortOffset); +addRegexToken('ZZ', matchShortOffset); +addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); +}); + +// HELPERS + +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var chunkOffset = /([\+\-]|\d\d)/gi; + +function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); + + if (matches === null) { + return null; + } + + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; +} + +// Return a moment from input, that is local/utc/zone equivalent to model. +function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } +} + +function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; +} + +// HOOKS + +// This function will be called whenever a moment is mutated. +// It is intended to keep the offset in sync with the timezone. +hooks.updateOffset = function () {}; + +// MOMENTS + +// keepLocalTime = true means only change the timezone, without +// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> +// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset +// +0200, so we adjust the time as needed, to be valid. +// +// Keeping the time actually adds/subtracts (one hour) +// from the actual represented time. That is why we call updateOffset +// a second time. In case it wants us to change the offset again +// _changeInProgress == true case, then we have to adjust, because +// there is no such time in the given timezone. +function getSetOffset (input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } +} + +function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } +} + +function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); +} + +function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; +} + +function setOffsetToParsedOffset () { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } + else { + this.utcOffset(0, true); + } + } + return this; +} + +function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; +} + +function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); +} + +function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; +} + +function isLocal () { + return this.isValid() ? !this._isUTC : false; +} + +function isUtcOffset () { + return this.isValid() ? this._isUTC : false; +} + +function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; +} + +// ASP.NET json date format regex +var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; + +// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html +// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere +// and further modified to allow for strings containing both week and day +var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; + +function createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; +} + +createDuration.fn = Duration.prototype; +createDuration.invalid = createInvalid$1; + +function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; +} + +function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; +} + +function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; +} + +// TODO: remove 'name' arg after deprecation is removed +function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; +} + +function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } +} + +var add = createAdder(1, 'add'); +var subtract = createAdder(-1, 'subtract'); + +function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; +} + +function calendar$1 (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); +} + +function clone () { + return new Moment(this); +} + +function isAfter (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } +} + +function isBefore (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } +} + +function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); +} + +function isSame (input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } +} + +function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); +} + +function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); +} + +function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); +} + +function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; +} + +hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; +hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + +function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); +} + +function toISOString() { + if (!this.isValid()) { + return null; + } + var m = this.clone().utc(); + if (m.year() < 0 || m.year() > 9999) { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); +} + +/** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ +function inspect () { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); +} + +function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); +} + +function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } +} + +function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); +} + +function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } +} + +function toNow (withoutSuffix) { + return this.to(createLocal(), withoutSuffix); +} + +// If passed a locale key, it will set the locale for this +// instance. Otherwise, it will return the locale configuration +// variables for this instance. +function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } +} + +var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } +); + +function localeData () { + return this._locale; +} + +function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; +} + +function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); +} + +function valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); +} + +function unix () { + return Math.floor(this.valueOf() / 1000); +} + +function toDate () { + return new Date(this.valueOf()); +} + +function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; +} + +function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; +} + +function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; +} + +function isValid$2 () { + return isValid(this); +} + +function parsingFlags () { + return extend({}, getParsingFlags(this)); +} + +function invalidAt () { + return getParsingFlags(this).overflow; +} + +function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; +} + +// FORMATTING + +addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; +}); + +addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; +}); + +function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); +} + +addWeekYearFormatToken('gggg', 'weekYear'); +addWeekYearFormatToken('ggggg', 'weekYear'); +addWeekYearFormatToken('GGGG', 'isoWeekYear'); +addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + +// ALIASES + +addUnitAlias('weekYear', 'gg'); +addUnitAlias('isoWeekYear', 'GG'); + +// PRIORITY + +addUnitPriority('weekYear', 1); +addUnitPriority('isoWeekYear', 1); + + +// PARSING + +addRegexToken('G', matchSigned); +addRegexToken('g', matchSigned); +addRegexToken('GG', match1to2, match2); +addRegexToken('gg', match1to2, match2); +addRegexToken('GGGG', match1to4, match4); +addRegexToken('gggg', match1to4, match4); +addRegexToken('GGGGG', match1to6, match6); +addRegexToken('ggggg', match1to6, match6); + +addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); +}); + +addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); +}); + +// MOMENTS + +function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); +} + +function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); +} + +function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); +} + +function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); +} + +function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } +} + +function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; +} + +// FORMATTING + +addFormatToken('Q', 0, 'Qo', 'quarter'); + +// ALIASES + +addUnitAlias('quarter', 'Q'); + +// PRIORITY + +addUnitPriority('quarter', 7); + +// PARSING + +addRegexToken('Q', match1); +addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; +}); + +// MOMENTS + +function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); +} + +// FORMATTING + +addFormatToken('D', ['DD', 2], 'Do', 'date'); + +// ALIASES + +addUnitAlias('date', 'D'); + +// PRIOROITY +addUnitPriority('date', 9); + +// PARSING + +addRegexToken('D', match1to2); +addRegexToken('DD', match1to2, match2); +addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict ? + (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : + locale._dayOfMonthOrdinalParseLenient; +}); + +addParseToken(['D', 'DD'], DATE); +addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); +}); + +// MOMENTS + +var getSetDayOfMonth = makeGetSet('Date', true); + +// FORMATTING + +addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + +// ALIASES + +addUnitAlias('dayOfYear', 'DDD'); + +// PRIORITY +addUnitPriority('dayOfYear', 4); + +// PARSING + +addRegexToken('DDD', match1to3); +addRegexToken('DDDD', match3); +addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); +}); + +// HELPERS + +// MOMENTS + +function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); +} + +// FORMATTING + +addFormatToken('m', ['mm', 2], 0, 'minute'); + +// ALIASES + +addUnitAlias('minute', 'm'); + +// PRIORITY + +addUnitPriority('minute', 14); + +// PARSING + +addRegexToken('m', match1to2); +addRegexToken('mm', match1to2, match2); +addParseToken(['m', 'mm'], MINUTE); + +// MOMENTS + +var getSetMinute = makeGetSet('Minutes', false); + +// FORMATTING + +addFormatToken('s', ['ss', 2], 0, 'second'); + +// ALIASES + +addUnitAlias('second', 's'); + +// PRIORITY + +addUnitPriority('second', 15); + +// PARSING + +addRegexToken('s', match1to2); +addRegexToken('ss', match1to2, match2); +addParseToken(['s', 'ss'], SECOND); + +// MOMENTS + +var getSetSecond = makeGetSet('Seconds', false); + +// FORMATTING + +addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); +}); + +addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); +}); + +addFormatToken(0, ['SSS', 3], 0, 'millisecond'); +addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; +}); +addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; +}); +addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; +}); +addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; +}); +addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; +}); +addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; +}); + + +// ALIASES + +addUnitAlias('millisecond', 'ms'); + +// PRIORITY + +addUnitPriority('millisecond', 16); + +// PARSING + +addRegexToken('S', match1to3, match1); +addRegexToken('SS', match1to3, match2); +addRegexToken('SSS', match1to3, match3); + +var token; +for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); +} + +function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); +} + +for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); +} +// MOMENTS + +var getSetMillisecond = makeGetSet('Milliseconds', false); + +// FORMATTING + +addFormatToken('z', 0, 0, 'zoneAbbr'); +addFormatToken('zz', 0, 0, 'zoneName'); + +// MOMENTS + +function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; +} + +function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; +} + +var proto = Moment.prototype; + +proto.add = add; +proto.calendar = calendar$1; +proto.clone = clone; +proto.diff = diff; +proto.endOf = endOf; +proto.format = format; +proto.from = from; +proto.fromNow = fromNow; +proto.to = to; +proto.toNow = toNow; +proto.get = stringGet; +proto.invalidAt = invalidAt; +proto.isAfter = isAfter; +proto.isBefore = isBefore; +proto.isBetween = isBetween; +proto.isSame = isSame; +proto.isSameOrAfter = isSameOrAfter; +proto.isSameOrBefore = isSameOrBefore; +proto.isValid = isValid$2; +proto.lang = lang; +proto.locale = locale; +proto.localeData = localeData; +proto.max = prototypeMax; +proto.min = prototypeMin; +proto.parsingFlags = parsingFlags; +proto.set = stringSet; +proto.startOf = startOf; +proto.subtract = subtract; +proto.toArray = toArray; +proto.toObject = toObject; +proto.toDate = toDate; +proto.toISOString = toISOString; +proto.inspect = inspect; +proto.toJSON = toJSON; +proto.toString = toString; +proto.unix = unix; +proto.valueOf = valueOf; +proto.creationData = creationData; + +// Year +proto.year = getSetYear; +proto.isLeapYear = getIsLeapYear; + +// Week Year +proto.weekYear = getSetWeekYear; +proto.isoWeekYear = getSetISOWeekYear; + +// Quarter +proto.quarter = proto.quarters = getSetQuarter; + +// Month +proto.month = getSetMonth; +proto.daysInMonth = getDaysInMonth; + +// Week +proto.week = proto.weeks = getSetWeek; +proto.isoWeek = proto.isoWeeks = getSetISOWeek; +proto.weeksInYear = getWeeksInYear; +proto.isoWeeksInYear = getISOWeeksInYear; + +// Day +proto.date = getSetDayOfMonth; +proto.day = proto.days = getSetDayOfWeek; +proto.weekday = getSetLocaleDayOfWeek; +proto.isoWeekday = getSetISODayOfWeek; +proto.dayOfYear = getSetDayOfYear; + +// Hour +proto.hour = proto.hours = getSetHour; + +// Minute +proto.minute = proto.minutes = getSetMinute; + +// Second +proto.second = proto.seconds = getSetSecond; + +// Millisecond +proto.millisecond = proto.milliseconds = getSetMillisecond; + +// Offset +proto.utcOffset = getSetOffset; +proto.utc = setOffsetToUTC; +proto.local = setOffsetToLocal; +proto.parseZone = setOffsetToParsedOffset; +proto.hasAlignedHourOffset = hasAlignedHourOffset; +proto.isDST = isDaylightSavingTime; +proto.isLocal = isLocal; +proto.isUtcOffset = isUtcOffset; +proto.isUtc = isUtc; +proto.isUTC = isUtc; + +// Timezone +proto.zoneAbbr = getZoneAbbr; +proto.zoneName = getZoneName; + +// Deprecations +proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); +proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); +proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); +proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); +proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + +function createUnix (input) { + return createLocal(input * 1000); +} + +function createInZone () { + return createLocal.apply(null, arguments).parseZone(); +} + +function preParsePostFormat (string) { + return string; +} + +var proto$1 = Locale.prototype; + +proto$1.calendar = calendar; +proto$1.longDateFormat = longDateFormat; +proto$1.invalidDate = invalidDate; +proto$1.ordinal = ordinal; +proto$1.preparse = preParsePostFormat; +proto$1.postformat = preParsePostFormat; +proto$1.relativeTime = relativeTime; +proto$1.pastFuture = pastFuture; +proto$1.set = set; + +// Month +proto$1.months = localeMonths; +proto$1.monthsShort = localeMonthsShort; +proto$1.monthsParse = localeMonthsParse; +proto$1.monthsRegex = monthsRegex; +proto$1.monthsShortRegex = monthsShortRegex; + +// Week +proto$1.week = localeWeek; +proto$1.firstDayOfYear = localeFirstDayOfYear; +proto$1.firstDayOfWeek = localeFirstDayOfWeek; + +// Day of Week +proto$1.weekdays = localeWeekdays; +proto$1.weekdaysMin = localeWeekdaysMin; +proto$1.weekdaysShort = localeWeekdaysShort; +proto$1.weekdaysParse = localeWeekdaysParse; + +proto$1.weekdaysRegex = weekdaysRegex; +proto$1.weekdaysShortRegex = weekdaysShortRegex; +proto$1.weekdaysMinRegex = weekdaysMinRegex; + +// Hours +proto$1.isPM = localeIsPM; +proto$1.meridiem = localeMeridiem; + +function get$1 (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); +} + +function listMonthsImpl (format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; +} + +// () +// (5) +// (fmt, 5) +// (fmt) +// (true) +// (true, 5) +// (true, fmt, 5) +// (true, fmt) +function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; +} + +function listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); +} + +function listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); +} + +function listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); +} + +function listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); +} + +function listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); +} + +getSetGlobalLocale('en', { + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } +}); + +// Side effect imports +hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); +hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); + +var mathAbs = Math.abs; + +function abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; +} + +function addSubtract$1 (duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); +} + +// supports only 2.0-style add(1, 's') or add(duration) +function add$1 (input, value) { + return addSubtract$1(this, input, value, 1); +} + +// supports only 2.0-style subtract(1, 's') or subtract(duration) +function subtract$1 (input, value) { + return addSubtract$1(this, input, value, -1); +} + +function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } +} + +function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; +} + +function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; +} + +function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; +} + +function as (units) { + if (!this.isValid()) { + return NaN; + } + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } +} + +// TODO: Use this.as('ms')? +function valueOf$1 () { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); +} + +function makeAs (alias) { + return function () { + return this.as(alias); + }; +} + +var asMilliseconds = makeAs('ms'); +var asSeconds = makeAs('s'); +var asMinutes = makeAs('m'); +var asHours = makeAs('h'); +var asDays = makeAs('d'); +var asWeeks = makeAs('w'); +var asMonths = makeAs('M'); +var asYears = makeAs('y'); + +function get$2 (units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; +} + +function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; +} + +var milliseconds = makeGetter('milliseconds'); +var seconds = makeGetter('seconds'); +var minutes = makeGetter('minutes'); +var hours = makeGetter('hours'); +var days = makeGetter('days'); +var months = makeGetter('months'); +var years = makeGetter('years'); + +function weeks () { + return absFloor(this.days() / 7); +} + +var round = Math.round; +var thresholds = { + ss: 44, // a few seconds to seconds + s : 45, // seconds to minute + m : 45, // minutes to hour + h : 22, // hours to day + d : 26, // days to month + M : 11 // months to year +}; + +// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize +function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); +} + +function relativeTime$1 (posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds <= thresholds.ss && ['s', seconds] || + seconds < thresholds.s && ['ss', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); +} + +// This function allows you to set the rounding function for relative time strings +function getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; +} + +// This function allows you to set a threshold for relative time strings +function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; +} + +function humanize (withSuffix) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); +} + +var abs$1 = Math.abs; + +function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); +} + +var proto$2 = Duration.prototype; + +proto$2.isValid = isValid$1; +proto$2.abs = abs; +proto$2.add = add$1; +proto$2.subtract = subtract$1; +proto$2.as = as; +proto$2.asMilliseconds = asMilliseconds; +proto$2.asSeconds = asSeconds; +proto$2.asMinutes = asMinutes; +proto$2.asHours = asHours; +proto$2.asDays = asDays; +proto$2.asWeeks = asWeeks; +proto$2.asMonths = asMonths; +proto$2.asYears = asYears; +proto$2.valueOf = valueOf$1; +proto$2._bubble = bubble; +proto$2.get = get$2; +proto$2.milliseconds = milliseconds; +proto$2.seconds = seconds; +proto$2.minutes = minutes; +proto$2.hours = hours; +proto$2.days = days; +proto$2.weeks = weeks; +proto$2.months = months; +proto$2.years = years; +proto$2.humanize = humanize; +proto$2.toISOString = toISOString$1; +proto$2.toString = toISOString$1; +proto$2.toJSON = toISOString$1; +proto$2.locale = locale; +proto$2.localeData = localeData; + +// Deprecations +proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); +proto$2.lang = lang; + +// Side effect imports + +// FORMATTING + +addFormatToken('X', 0, 0, 'unix'); +addFormatToken('x', 0, 0, 'valueOf'); + +// PARSING + +addRegexToken('x', matchSigned); +addRegexToken('X', matchTimestamp); +addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); +}); +addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); +}); + +// Side effect imports + + +hooks.version = '2.18.1'; + +setHookCallback(createLocal); + +hooks.fn = proto; +hooks.min = min; +hooks.max = max; +hooks.now = now; +hooks.utc = createUTC; +hooks.unix = createUnix; +hooks.months = listMonths; +hooks.isDate = isDate; +hooks.locale = getSetGlobalLocale; +hooks.invalid = createInvalid; +hooks.duration = createDuration; +hooks.isMoment = isMoment; +hooks.weekdays = listWeekdays; +hooks.parseZone = createInZone; +hooks.localeData = getLocale; +hooks.isDuration = isDuration; +hooks.monthsShort = listMonthsShort; +hooks.weekdaysMin = listWeekdaysMin; +hooks.defineLocale = defineLocale; +hooks.updateLocale = updateLocale; +hooks.locales = listLocales; +hooks.weekdaysShort = listWeekdaysShort; +hooks.normalizeUnits = normalizeUnits; +hooks.relativeTimeRounding = getSetRelativeTimeRounding; +hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; +hooks.calendarFormat = getCalendarFormat; +hooks.prototype = proto; + +return hooks; + +}))); diff --git a/master1.kuzniatsou.local.conf b/master1.kuzniatsou.local.conf new file mode 100644 index 0000000..f80a819 --- /dev/null +++ b/master1.kuzniatsou.local.conf @@ -0,0 +1,34 @@ + + ServerName master1.kuzniatsou.local + + ## Vhost docroot + DocumentRoot "/usr/share/puppetexplorer/dist" + + ## Directories, there should at least be a declaration for /usr/share/puppetexplorer + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Require all granted + + + ## Logging + ErrorLog "/var/log/httpd/master.kuzniatsou.local_error_ssl.log" + ServerSignature Off + CustomLog "/var/log/httpd/master.kuzniatsou.local_access_ssl.log" combined + + ## Proxy rules + ProxyRequests Off + ProxyPreserveHost Off + ProxyPass /api/pdb/query http://localhost:8080/pdb/query + ProxyPassReverse /api/pdb/query http://localhost:8080/pdb/query + ProxyPass /api/pdb/meta http://localhost:8080/pdb/meta + ProxyPassReverse /api/pdb/meta http://localhost:8080/pdb/meta + ProxyPass /api/metrics http://localhost:8080/metrics + ProxyPassReverse /api/metrics http://localhost:8080/metrics + + RewriteEngine On + RewriteRule ^/api/metrics/v1/mbeans/puppetlabs.puppetdb.query.population:type=default,name=(.*)$ http://%{HTTP_HOST}/api/metrics/v1/mbeans/puppetlabs.puppetdb.population:name=$1 [R=301,L] + + + From 8363322669c672d05ed60447cefa26386b4331f6 Mon Sep 17 00:00:00 2001 From: ngkuznetsov Date: Mon, 17 Apr 2017 04:20:01 +0300 Subject: [PATCH 4/4] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index baadf4d..326c8d1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ > **Table of contents:** +> - [**dist**][0] - directory of puppetexplorer app built by Grunt > - [**environments**][1] - directory with 2 environments for node1 and node2 > - [**manuals**][2] - dirctory with manuals > - [**vagrant_scripts**][3] - Vagrant directory of scripts for provisioning VMs @@ -9,6 +10,7 @@ > - [**Vagrantfile**][5] - Vagrantfile. Provisioning dns, master, node1, node2 VMs > - [**[agent]-node1-puppet.conf**][6] - configuration file of puppet on node1 > - [**[agent]-node2-puppet.conf**][7] - configuration file of puppet on node2 +> - [**master1.kuzniatsou.local.conf**][12] - Apache virt host for puppetexplorer > - [**puppet.conf**][8] - configuration file of puppet on master > - [**puppetdb.conf**][9] - configuration file of puppetdb @@ -24,6 +26,7 @@ ### Main dashboard of the master1.kuzniatsou.local. ![alt text](screens/master_main.png "master") +[0]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/dist [1]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/environments [2]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/manuals [3]: https://github.com/ngkuznetsov/lesson13/tree/mikalai_kuzniatsou/vagrant_scripts @@ -35,3 +38,5 @@ [9]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/puppetdb.conf [10]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt [11]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/manuals/Manual_installing_puppetdb_and_puppetexplorer.txt +[12]: https://github.com/ngkuznetsov/lesson13/blob/mikalai_kuzniatsou/master1.kuzniatsou.local.conf +