diff options
122 files changed, 3680 insertions, 299 deletions
diff --git a/.gemspec b/.gemspec new file mode 100644 index 0000000..e274950 --- /dev/null +++ b/.gemspec @@ -0,0 +1,40 @@ +# +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "puppetmodule-stdlib" + + s.version = "4.0.2" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Puppet Labs"] + s.date = "2013-04-12" + s.description = [ 'This Gem format of the stdlib module is intended to make', + 'it easier for _module authors_ to resolve dependencies', + 'using a Gemfile when running automated testing jobs like', + 'Travis or Jenkins. The recommended best practice for', + 'installation by end users is to use the `puppet module', + 'install` command to install stdlib from the [Puppet', + 'Forge](http://forge.puppetlabs.com/puppetlabs/stdlib).' ].join(' ') + s.email = "puppet-dev@puppetlabs.com" + s.executables = [] + s.files = [ 'CHANGELOG', 'CONTRIBUTING.md', 'Gemfile', 'LICENSE', 'Modulefile', + 'README.markdown', 'README_DEVELOPER.markdown', 'RELEASE_PROCESS.markdown', + 'Rakefile', 'spec/spec.opts' ] + s.files += Dir['lib/**/*.rb'] + Dir['manifests/**/*.pp'] + Dir['tests/**/*.pp'] + Dir['spec/**/*.rb'] + s.homepage = "http://forge.puppetlabs.com/puppetlabs/stdlib" + s.rdoc_options = ["--title", "Puppet Standard Library Development Gem", "--main", "README.markdown", "--line-numbers"] + s.require_paths = ["lib"] + s.rubyforge_project = "puppetmodule-stdlib" + s.rubygems_version = "1.8.24" + s.summary = "This gem provides a way to make the standard library available for other module spec testing tasks." + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + else + end + else + end +end @@ -1,6 +1,7 @@ pkg/ .DS_Store coverage/ +spec/fixtures/ Gemfile.lock .bundle/ vendor/bundle/ diff --git a/.project b/.project new file mode 100644 index 0000000..4e2c033 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>stdlib</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.cloudsmith.geppetto.pp.dsl.ui.modulefileBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.xtext.ui.shared.xtextBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.cloudsmith.geppetto.pp.dsl.ui.puppetNature</nature> + <nature>org.eclipse.xtext.ui.shared.xtextNature</nature> + </natures> +</projectDescription> diff --git a/.travis.yml b/.travis.yml index 7e40b3f..34d8cc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,22 @@ +--- language: ruby bundler_args: --without development -script: "bundle exec rake spec SPEC_OPTS='--color --format documentation'" +script: "bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--color --format documentation'" rvm: - 1.8.7 + - 1.9.3 + - 2.0.0 + - ruby-head env: - - PUPPET_VERSION=">= 3.0.0" - - PUPPET_VERSION="~> 2.7.0" - - PUPPET_VERSION=2.7.13 - - PUPPET_VERSION=2.7.6 - - PUPPET_VERSION=2.6.9 + - PUPPET_GEM_VERSION=">= 3.0.0" matrix: + fast_finish: true allow_failures: - rvm: 2.0.0 + - rvm: ruby-head include: - - rvm: 2.0.0 - env: PUPPET_VERSION=">= 3.0.0" - - rvm: 1.9.3 - env: PUPPET_VERSION=">= 3.0.0" + - rvm: 1.8.7 + env: PUPPET_GEM_VERSION="~> 2.7" notifications: email: false webhooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0ec67..f132649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,144 @@ This is a supported release --- +##### 2013-05-06 - Jeff McCune <jeff@puppetlabs.com> - 4.1.0 + + * (#20582) Restore facter\_dot\_d to stdlib for PE users (3b887c8) + * (maint) Update Gemfile with GEM\_FACTER\_VERSION (f44d535) + +##### 2013-05-06 - Alex Cline <acline@us.ibm.com> - 4.1.0 + + * Terser method of string to array conversion courtesy of ethooz. (d38bce0) + +##### 2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0 + + * Refactor ensure\_resource expectations (b33cc24) + +##### 2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0 + + * Changed str-to-array conversion and removed abbreviation. (de253db) + +##### 2013-05-03 - Alex Cline <acline@us.ibm.com> 4.1.0 + + * (#20548) Allow an array of resource titles to be passed into the ensure\_resource function (e08734a) + +##### 2013-05-02 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.1.0 + + * Add a dirname function (2ba9e47) + +##### 2013-04-29 - Mark Smith-Guerrero <msmithgu@gmail.com> - 4.1.0 + + * (maint) Fix a small typo in hash() description (928036a) + +##### 2013-04-12 - Jeff McCune <jeff@puppetlabs.com> - 4.0.2 + + * Update user information in gemspec to make the intent of the Gem clear. + +##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.1 + + * Fix README function documentation (ab3e30c) + +##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * stdlib 4.0 drops support with Puppet 2.7 + * stdlib 4.0 preserves support with Puppet 3 + +##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * Add ability to use puppet from git via bundler (9c5805f) + +##### 2013-04-10 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * (maint) Make stdlib usable as a Ruby GEM (e81a45e) + +##### 2013-04-10 - Erik Dalén <dalen@spotify.com> - 4.0.0 + + * Add a count function (f28550e) + +##### 2013-03-31 - Amos Shapira <ashapira@atlassian.com> - 4.0.0 + + * (#19998) Implement any2array (7a2fb80) + +##### 2013-03-29 - Steve Huff <shuff@vecna.org> - 4.0.0 + + * (19864) num2bool match fix (8d217f0) + +##### 2013-03-20 - Erik Dalén <dalen@spotify.com> - 4.0.0 + + * Allow comparisons of Numeric and number as String (ff5dd5d) + +##### 2013-03-26 - Richard Soderberg <rsoderberg@mozilla.com> - 4.0.0 + + * add suffix function to accompany the prefix function (88a93ac) + +##### 2013-03-19 - Kristof Willaert <kristof.willaert@gmail.com> - 4.0.0 + + * Add floor function implementation and unit tests (0527341) + +##### 2012-04-03 - Eric Shamow <eric@puppetlabs.com> - 4.0.0 + + * (#13610) Add is\_function\_available to stdlib (961dcab) + +##### 2012-12-17 - Justin Lambert <jlambert@eml.cc> - 4.0.0 + + * str2bool should return a boolean if called with a boolean (5d5a4d4) + +##### 2012-10-23 - Uwe Stuehler <ustuehler@team.mobile.de> - 4.0.0 + + * Fix number of arguments check in flatten() (e80207b) + +##### 2013-03-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * Add contributing document (96e19d0) + +##### 2013-03-04 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.0.0 + + * Add missing documentation for validate\_augeas and validate\_cmd to README.markdown (a1510a1) + +##### 2013-02-14 - Joshua Hoblitt <jhoblitt@cpan.org> - 4.0.0 + + * (#19272) Add has\_element() function (95cf3fe) + +##### 2013-02-07 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.0.0 + + * validate\_cmd(): Use Puppet::Util::Execution.execute when available (69248df) + +##### 2012-12-06 - Raphaël Pinson <raphink@gmail.com> - 4.0.0 + + * Add validate\_augeas function (3a97c23) + +##### 2012-12-06 - Raphaël Pinson <raphink@gmail.com> - 4.0.0 + + * Add validate\_cmd function (6902cc5) + +##### 2013-01-14 - David Schmitt <david@dasz.at> - 4.0.0 + + * Add geppetto project definition (b3fc0a3) + +##### 2013-01-02 - Jaka Hudoklin <jakahudoklin@gmail.com> - 4.0.0 + + * Add getparam function to get defined resource parameters (20e0e07) + +##### 2013-01-05 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * (maint) Add Travis CI Support (d082046) + +##### 2012-12-04 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0 + + * Clarify that stdlib 3 supports Puppet 3 (3a6085f) + +##### 2012-11-30 - Erik Dalén <dalen@spotify.com> - 4.0.0 + + * maint: style guideline fixes (7742e5f) + +##### 2012-11-09 - James Fryman <james@frymanet.com> - 4.0.0 + + * puppet-lint cleanup (88acc52) + +##### 2012-11-06 - Joe Julian <me@joejulian.name> - 4.0.0 + + * Add function, uriescape, to URI.escape strings. Redmine #17459 (fd52b8d) + ##### 2012-09-18 - Chad Metcalf <chad@wibidata.com> - 3.2.0 * Add an ensure\_packages function. (8a8c09e) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5280da1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# How to contribute + +Third-party patches are essential for keeping stdlib great. We simply can't +access the huge number of platforms and myriad configurations for running +stdlib. We want to keep it as easy as possible to contribute changes that +get things working 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. + +## Getting Started + +* Make sure you have a [Jira account](http://tickets.puppetlabs.com) +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. + * This is usually the master branch. + * Only target release branches if you are certain your fix must be on that + branch. + * To quickly create a topic branch based on master; `git branch + fix/master/my_contribution master` then checkout the new branch with `git + checkout fix/master/my_contribution`. Please avoid working directly on the + `master` branch. +* Make commits of logical units. +* Check for unnecessary whitespace with `git diff --check` before committing. +* Make sure your commit messages are in the proper format. + +```` + (#99999) Make the example in CONTRIBUTING imperative and concrete + + Without this patch applied the example commit message in the CONTRIBUTING + document is not a concrete example. This is a problem because the + contributor is left to imagine what the commit message should look like + based on a description rather than an example. This patch fixes the + problem by making the example concrete and imperative. + + The first line is a real life imperative statement with a ticket number + from our issue tracker. The body describes the behavior without the patch, + why this is a problem, and how the patch fixes the problem when applied. +```` + +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Submitting Changes + +* Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository in the puppetlabs organization. +* Update your ticket to mark that you have submitted code and are ready for it to be reviewed. + * Include a link to the pull request in the ticket + +# Additional Resources + +* [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) +* [Bug tracker (Jira)](http://tickets.puppetlabs.com) +* [Contributor License Agreement](http://links.puppetlabs.com/cla) +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) +* #puppet-dev IRC channel on freenode.org @@ -1,6 +1,6 @@ name 'puppetlabs-stdlib' -version '3.2.1' -source 'git://github.com/puppetlabs/puppetlabs-stdlib' +version '4.1.0' +source 'git://github.com/puppetlabs/puppetlabs-stdlib.git' author 'puppetlabs' license 'Apache 2.0' summary 'Puppet Module Standard Library' diff --git a/README.markdown b/README.markdown index 130753d..76c546f 100644 --- a/README.markdown +++ b/README.markdown @@ -1,5 +1,7 @@ # Puppet Labs Standard Library # +[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-stdlib.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-stdlib) + This module provides a "standard library" of resources for developing Puppet Modules. This modules will include the following additions to Puppet @@ -15,7 +17,7 @@ Puppet Labs writes and distributes will make heavy use of this standard library. To report or research a bug with any part of this module, please go to -[http://projects.puppetlabs.com/projects/stdlib](http://projects.puppetlabs.com/projects/stdlib) +[http://tickets.puppetlabs.com/browse/PUP](http://tickets.puppetlabs.com/browse/PUP) # Versions # @@ -26,9 +28,12 @@ older versions of Puppet Enterprise that Puppet Labs still supports will have bugfix maintenance branches periodically "merged up" into master. The current list of integration branches are: - * v2.1.x (v2.1.1 released in PE 1.2, 1.2.1, 1.2.3, 1.2.4) + * v2.1.x (v2.1.1 released in PE 1) * v2.2.x (Never released as part of PE, only to the Forge) - * v2.3.x (Released in PE 2.5.x) + * v2.3.x (Released in PE 2) + * v3.0.x (Released in PE 3) + * v4.0.x (Maintains compatibility with v3.x despite the major semantic version bump. Compatible with Puppet 2.7.x) + * v5.x (To be released when stdlib can drop support for Puppet 2.7.x. Please see [this discussion](https://github.com/puppetlabs/puppetlabs-stdlib/pull/176#issuecomment-30251414)) * master (mainline development branch) The first Puppet Enterprise version including the stdlib module is Puppet @@ -36,6 +41,12 @@ Enterprise 1.2. # Compatibility # +Puppet Versions | < 2.6 | 2.6 | 2.7 | 3.x | +:---------------|:-----:|:---:|:---:|:----: +**stdlib 2.x** | no | **yes** | **yes** | no +**stdlib 3.x** | no | no | **yes** | **yes** +**stdlib 4.x** | no | no | **yes** | **yes** + The stdlib module does not work with Puppet versions released prior to Puppet 2.6.0. @@ -46,14 +57,40 @@ All stdlib releases in the 2.0 major version support Puppet 2.6 and Puppet 2.7. ## stdlib 3.x ## The 3.0 major release of stdlib drops support for Puppet 2.6. Stdlib 3.x -supports Puppet 2.7. +supports Puppet 2 and Puppet 3. + +## stdlib 4.x ## + +The 4.0 major release of stdlib was intended to drop support for Puppet 2.7, +but the impact on end users was too high. The decision was made to treat +stdlib 4.x as a continuation of stdlib 3.x support. Stdlib 4.x supports Puppet +2.7 and 3. Notably, ruby 1.8.5 is no longer supported though ruby +1.8.7, 1.9.3, and 2.0.0 are fully supported. # 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. +Returns the absolute value of a number, for example -34.56 becomes +34.56. Takes a single integer and float value as an argument. + + +- *Type*: rvalue + +any2array +--------- +This converts any object to an array containing that object. Empty argument +lists are converted to an empty array. Arrays are left untouched. Hashes are +converted to arrays of alternating keys and values. + + +- *Type*: rvalue + +base64 +-------- +Converts a string to and from base64 encoding. +Requires an action ['encode','decode'] and either a plain or base64 encoded +string - *Type*: rvalue @@ -78,9 +115,9 @@ Requires either a single string or an array as an input. 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. +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 @@ -96,6 +133,30 @@ Requires a string or array of strings as input. - *Type*: rvalue +concat +------ +Appends the contents of array 2 onto array 1. + +*Example:* + + concat(['1','2','3'],['4','5','6']) + +Would result in: + + ['1','2','3','4','5','6'] + + +- *Type*: rvalue + +count +----- +Takes an array as first argument and an optional second argument. +Count the number of elements in array that matches second argument. +If called with only an array it counts the number of elements that are not nil/undef. + + +- *Type*: rvalue + defined_with_params ------------------- Takes a resource reference and an optional hash of attributes. @@ -116,13 +177,19 @@ to the catalog, and false otherwise. delete ------ -Deletes a selected element from an array. +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') + delete(['a','b','c','b'], 'b') + Would return: ['a','c'] -Would return: ['a','c'] + delete({'a'=>1,'b'=>2,'c'=>3}, 'b') + Would return: {'a'=>1,'c'=>3} + + delete('abracadabra', 'bra') + Would return: 'acada' - *Type*: rvalue @@ -140,6 +207,57 @@ Would return: ['a','c'] - *Type*: rvalue +delete_values +------------- +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'} + + +- *Type*: rvalue + +delete_undef_values +------------------- +Deletes all instances of the undef value from an array or hash. + +*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] + +- *Type*: rvalue + +difference +---------- +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"] + +dirname +------- +Returns the `dirname` of a path. + +*Examples:* + + dirname('/path/to/a/file.ext') + +Would return: '/path/to/a' + downcase -------- Converts the case of a string or all strings in an array to lower case. @@ -154,6 +272,13 @@ Returns true if the variable is empty. - *Type*: rvalue +ensure_packages +--------------- +Takes a list of packages and only installs them if they don't already exist. + + +- *Type*: statement + ensure_resource --------------- Takes a resource type, title, and a list of attributes that describe a @@ -165,16 +290,41 @@ resource. This example only creates the resource if it does not already exist: - ensure_resource('user, 'dan', {'ensure' => 'present' }) + 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'}) + - *Type*: statement +file_line +--------- +This resource ensures that a given line is contained within a file. You can also use +"match" to replace existing lines. + +*Examples:* + + file_line { 'sudo_rule': + path => '/etc/sudoers', + line => '%sudo ALL=(ALL) ALL', + } + + file_line { 'change_mount': + path => '/etc/fstab', + line => '10.0.0.1:/vol/data /opt/data nfs defaults 0 0', + match => '^172.16.17.2:/vol/old', + } + +- *Type*: resource + flatten ------- This function flattens any deeply nested arrays and returns a single flat array @@ -189,6 +339,14 @@ Would return: ['a','b','c'] - *Type*: rvalue +floor +----- +Returns the largest integer less or equal to the argument. +Takes a single numeric value as an argument. + + +- *Type*: rvalue + fqdn_rotate ----------- Rotates an array a random number of times based on a nodes fqdn. @@ -207,6 +365,27 @@ Example: - *Type*: rvalue +getparam +-------- +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 + + +- *Type*: rvalue + getvar ------ Lookup a variable in a remote namespace. @@ -241,6 +420,48 @@ Would return: - *Type*: rvalue +has_interface_with +------------------ +Returns boolean based on kind and value: +* macaddress +* netmask +* ipaddress +* network + +*Examples:* + + has_interface_with("macaddress", "x:x:x:x:x:x") + has_interface_with("ipaddress", "127.0.0.1") => true + +etc. + +If no "kind" is given, then the presence of the interface is checked: + + has_interface_with("lo") => true + + +- *Type*: rvalue + +has_ip_address +-------------- +Returns true if the client has the requested IP address on some interface. + +This function iterates through the 'interfaces' fact and checks the +'ipaddress_IFACE' facts, performing a simple string comparison. + + +- *Type*: rvalue + +has_ip_network +-------------- +Returns true if the client has an IP address within the requested network. + +This function iterates through the 'interfaces' fact and checks the +'network_IFACE' facts, performing a simple string comparision. + + +- *Type*: rvalue + has_key ------- Determine if a hash has a certain key value. @@ -261,7 +482,7 @@ Example: hash ---- -This function converts and array into a hash. +This function converts an array into a hash. *Examples:* @@ -272,10 +493,25 @@ Would return: {'a'=>1,'b'=>2,'c'=>3} - *Type*: rvalue +intersection +----------- +This function returns an array an intersection of two. + +*Examples:* + + intersection(["a","b","c"],["b","c","d"]) + +Would return: ["b","c"] + is_array -------- Returns true if the variable passed to this function is an array. +- *Type*: rvalue + +is_bool +-------- +Returns true if the variable passed to this function is a boolean. - *Type*: rvalue @@ -283,13 +519,19 @@ is_domain_name -------------- Returns true if the string passed to this function is a syntactically correct domain name. - - *Type*: rvalue is_float -------- Returns true if the variable passed to this function is a float. +- *Type*: rvalue + +is_function_available +--------------------- +This function accepts a string as an argument, determines whether the +Puppet runtime has access to a function by that name. It returns a +true if the function exists, false if not. - *Type*: rvalue @@ -297,47 +539,41 @@ is_hash ------- Returns true if the variable passed to this function is a hash. - - *Type*: rvalue is_integer ---------- Returns true if the variable returned to this string is an integer. - - *Type*: rvalue is_ip_address ------------- Returns true if the string passed to this function is a valid IP address. - - *Type*: rvalue is_mac_address -------------- Returns true if the string passed to this function is a valid mac address. - - *Type*: rvalue is_numeric ---------- Returns true if the variable passed to this function is a number. - - *Type*: rvalue is_string --------- Returns true if the variable passed to this function is a string. - - *Type*: rvalue join ---- -This function joins an array into a string using a seperator. +This function joins an array into a string using a separator. *Examples:* @@ -345,6 +581,19 @@ This function joins an array into a string using a seperator. Would result in: "a,b,c" +- *Type*: rvalue + +join_keys_to_values +------------------- +This function joins each key of a hash to that key's corresponding value with a +separator. Keys and values are cast to strings. The return value is an array in +which each element is one joined key/value pair. + +*Examples:* + + join_keys_to_values({'a'=>1,'b'=>2}, " is ") + +Would result in: ["a is 1","b is 2"] - *Type*: rvalue @@ -352,7 +601,6 @@ keys ---- Returns the keys of a hash as an array. - - *Type*: rvalue loadyaml @@ -371,6 +619,12 @@ lstrip ------ Strips leading spaces to the left of a string. +- *Type*: rvalue + +max +--- +Returns the highest value of all arguments. +Requires at least one argument. - *Type*: rvalue @@ -388,7 +642,6 @@ Would return: true Would return: false - - *Type*: rvalue merge @@ -397,23 +650,28 @@ Merges two or more hashes together and returns the resulting hash. For example: - $hash1 = {'one' => 1, 'two', => 2} - $hash2 = {'two' => 'dos', 'three', => 'tres'} + $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." +- *Type*: rvalue +min +--- +Returns the lowest value of all arguments. +Requires at least one argument. - *Type*: rvalue num2bool -------- -This function converts a number into a true boolean. Zero becomes false. Numbers -higher then 0 become true. - +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. - *Type*: rvalue @@ -422,7 +680,6 @@ parsejson This function accepts JSON as a string and converts into the correct Puppet structure. - - *Type*: rvalue parseyaml @@ -430,6 +687,22 @@ parseyaml This function accepts YAML as a string and converts it into the correct Puppet structure. +- *Type*: rvalue + +pick +---- +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. - *Type*: rvalue @@ -437,13 +710,12 @@ prefix ------ This function applies a prefix to all elements in an array. -*Examles:* +*Examples:* prefix(['a','b','c'], 'p') Will return: ['pa','pb','pc'] - - *Type*: rvalue range @@ -459,8 +731,8 @@ 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) +Will return: [0,1,2,3,4,5,6,7,8,9] - Zero padded strings are converted to +integers automatically range("a", "c") @@ -470,6 +742,21 @@ Will return: ["a","b","c"] Will return: ["host01", "host02", ..., "host09", "host10"] +- *Type*: rvalue + +reject +------ +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'] + - *Type*: rvalue @@ -477,35 +764,30 @@ reverse ------- Reverses the order of a string or array. - - *Type*: rvalue rstrip ------ Strips leading spaces to the right of the string. - - *Type*: rvalue shuffle ------- Randomizes the order of a string or array elements. - - *Type*: rvalue size ---- Returns the number of elements in a string or array. - - *Type*: rvalue sort ---- Sorts strings and arrays lexically. - - *Type*: rvalue squeeze @@ -513,12 +795,11 @@ squeeze Returns a new string where runs of the same character that occur in this set are replaced by a single character. - - *Type*: rvalue str2bool -------- -This converts a string to a boolean. This attempt to convert strings that +This converts a string to a boolean. This attempts to convert strings that contain things like: y, 1, t, true to 'true' and strings that contain things like: 0, f, n, false, no to 'false'. @@ -527,10 +808,10 @@ like: 0, f, n, false, no to 'false'. str2saltedsha512 ---------------- -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. +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. - *Type*: rvalue @@ -570,8 +851,7 @@ To return the date: %L - Millisecond of the second (000..999) %m - Month of the year (01..12) %M - Minute of the hour (00..59) - %n - Newline ( -) + %n - Newline (\n) %N - Fractional seconds digits, default is 9 digits (nanosecond) %3N millisecond (3 digits) %6N microsecond (6 digits) @@ -619,6 +899,19 @@ Would result in: "aaa" - *Type*: rvalue +suffix +------ +This function applies a suffix to all elements in an array. + +*Examples:* + + suffix(['a','b','c'], 'p') + +Will return: ['ap','bp','cp'] + + +- *Type*: rvalue + swapcase -------- This function will swap the existing case of a string. @@ -667,6 +960,17 @@ Returns the type when passed a variable. Type can be one of: - *Type*: rvalue +union +----- +This function returns a union of two arrays. + +*Examples:* + + union(["a","b","c"],["b","c","d"]) + +Would return: ["a","b","c","d"] + + unique ------ This function will remove duplicates from strings and arrays. @@ -705,6 +1009,14 @@ Will return: - *Type*: rvalue +uriescape +--------- +Urlencodes a string or array of strings. +Requires either a single string or an array as an input. + + +- *Type*: rvalue + validate_absolute_path ---------------------- Validate the string represents an absolute path in the filesystem. This function works @@ -751,6 +1063,39 @@ The following values will fail, causing compilation to abort: - *Type*: statement +validate_augeas +--------------- +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') + + + +- *Type*: statement + validate_bool ------------- Validate that all passed values are either true or false. Abort catalog @@ -773,6 +1118,28 @@ The following values will fail, causing compilation to abort: - *Type*: statement +validate_cmd +------------ +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 file as last argument. 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: + + validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content') + + + +- *Type*: statement + validate_hash ------------- Validate that all passed values are hash data structures. Abort catalog @@ -862,7 +1229,6 @@ The following values will fail, causing compilation to abort: validate_string($undefined) - - *Type*: statement values @@ -927,3 +1293,5 @@ Would result in: - *Type*: rvalue + +*This page autogenerated on 2013-04-11 13:54:25 -0700* diff --git a/README_SPECS.markdown b/README_SPECS.markdown new file mode 100644 index 0000000..917b631 --- /dev/null +++ b/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/RELEASE_PROCESS.markdown b/RELEASE_PROCESS.markdown index 3982c84..0f9328e 100644 --- a/RELEASE_PROCESS.markdown +++ b/RELEASE_PROCESS.markdown @@ -22,4 +22,3 @@ * 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. - @@ -1,2 +1,18 @@ require 'rubygems' require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] + +desc "Validate manifests, templates, and ruby files in lib." +task :validate do + Dir['manifests/**/*.pp'].each do |manifest| + sh "puppet parser validate --noop #{manifest}" + end + Dir['lib/**/*.rb'].each do |lib_file| + sh "ruby -c #{lib_file}" + end + Dir['templates/**/*.erb'].each do |template| + sh "erb -P -x -T '-' #{template} | ruby -c" + end +end diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb new file mode 100644 index 0000000..e414b20 --- /dev/null +++ b/lib/facter/facter_dot_d.rb @@ -0,0 +1,202 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d +# and /etc/puppetlabs/facter/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. +# +# In the case of scripts you can also create a file that +# contains a cache TTL. For foo.sh store the ttl as just +# a number in foo.sh.ttl +# +# The cache is stored in /tmp/facts_cache.yaml as a mode +# 600 file and will have the end result of not calling your +# fact scripts more often than is needed + +class Facter::Util::DotD + require 'yaml' + + def initialize(dir="/etc/facts.d", cache_file="/tmp/facts_cache.yml") + @dir = dir + @cache_file = cache_file + @cache = nil + @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml} + end + + def entries + Dir.entries(@dir).reject{|f| f =~ /^\.|\.ttl$/}.sort.map {|f| File.join(@dir, f) } + rescue + [] + end + + def fact_type(file) + extension = File.extname(file) + + type = @types[extension] || :unknown + + type = :script if type == :unknown && File.executable?(file) + + return type + end + + def txt_parser(file) + File.readlines(file).each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") + end + + def json_parser(file) + begin + require 'json' + rescue LoadError + retry if require 'rubygems' + raise + end + + JSON.load(File.read(file)).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}") + end + + def yaml_parser(file) + require 'yaml' + + YAML.load_file(file).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}") + end + + def script_parser(file) + result = cache_lookup(file) + ttl = cache_time(file) + + unless result + result = Facter::Util::Resolution.exec(file) + + if ttl > 0 + Facter.debug("Updating cache for #{file}") + cache_store(file, result) + cache_save! + end + else + Facter.debug("Using cached data for #{file}") + end + + result.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + + def cache_save! + cache = load_cache + File.open(@cache_file, "w", 0600) {|f| f.write(YAML.dump(cache)) } + rescue + end + + def cache_store(file, data) + load_cache + + @cache[file] = {:data => data, :stored => Time.now.to_i} + rescue + end + + def cache_lookup(file) + cache = load_cache + + return nil if cache.empty? + + ttl = cache_time(file) + + if cache[file] + now = Time.now.to_i + + return cache[file][:data] if ttl == -1 + return cache[file][:data] if (now - cache[file][:stored]) <= ttl + return nil + else + return nil + end + rescue + return nil + end + + def cache_time(file) + meta = file + ".ttl" + + return File.read(meta).chomp.to_i + rescue + return 0 + end + + def load_cache + unless @cache + if File.exist?(@cache_file) + @cache = YAML.load_file(@cache_file) + else + @cache = {} + end + end + + return @cache + rescue + @cache = {} + return @cache + end + + def create + entries.each do |fact| + type = fact_type(fact) + parser = "#{type}_parser" + + if respond_to?("#{type}_parser") + Facter.debug("Parsing #{fact} using #{parser}") + + send(parser, fact) + end + end + end +end + + +mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/) +if mdata + (major, minor, patch) = mdata.captures.map { |v| v.to_i } + if major < 2 + # Facter 1.7 introduced external facts support directly + unless major == 1 and minor > 6 + Facter::Util::DotD.new("/etc/facter/facts.d").create + Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + + # Windows has a different configuration directory that defaults to a vendor + # specific sub directory of the %COMMON_APPDATA% directory. + if Dir.const_defined? 'COMMON_APPDATA' then + windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') + Facter::Util::DotD.new(windows_facts_dot_d).create + end + end + end +end diff --git a/lib/facter/root_home.rb b/lib/facter/root_home.rb index 8249f7d..b4f87ff 100644 --- a/lib/facter/root_home.rb +++ b/lib/facter/root_home.rb @@ -17,3 +17,16 @@ end Facter.add(:root_home) do setcode { Facter::Util::RootHome.get_root_home } end + +Facter.add(:root_home) do + confine :kernel => :darwin + setcode do + str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root") + hash = {} + str.split("\n").each do |pair| + key,value = pair.split(/:/) + hash[key] = value + end + hash['dir'].strip + end +end diff --git a/lib/puppet/parser/functions/abs.rb b/lib/puppet/parser/functions/abs.rb index ade5462..11d2d7f 100644 --- a/lib/puppet/parser/functions/abs.rb +++ b/lib/puppet/parser/functions/abs.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:abs, :type => :rvalue, :doc => <<-EOS - Returns the absolute value of a number, for example -34.56 becomes + 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| diff --git a/lib/puppet/parser/functions/any2array.rb b/lib/puppet/parser/functions/any2array.rb new file mode 100644 index 0000000..e71407e --- /dev/null +++ b/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/lib/puppet/parser/functions/base64.rb b/lib/puppet/parser/functions/base64.rb new file mode 100644 index 0000000..617ba31 --- /dev/null +++ b/lib/puppet/parser/functions/base64.rb @@ -0,0 +1,37 @@ +module Puppet::Parser::Functions + + newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args| + + Base64 encode or decode a string based on the command and the string submitted + + Usage: + + $encodestring = base64('encode','thestring') + $decodestring = base64('decode','dGhlc3RyaW5n') + + ENDHEREDOC + + require 'base64' + + raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be = 2)") unless args.length == 2 + + actions = ['encode','decode'] + + unless actions.include?(args[0]) + raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'") + end + + unless args[1].is_a?(String) + raise Puppet::ParseError, ("base64(): the second argument must be a string to base64") + end + + case args[0] + when 'encode' + result = Base64.encode64(args[1]) + when 'decode' + result = Base64.decode64(args[1]) + end + + return result + end +end diff --git a/lib/puppet/parser/functions/chomp.rb b/lib/puppet/parser/functions/chomp.rb index c99d139..4564a00 100644 --- a/lib/puppet/parser/functions/chomp.rb +++ b/lib/puppet/parser/functions/chomp.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS' - Removes the record separator from the end of a string or an array of + 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 diff --git a/lib/puppet/parser/functions/chop.rb b/lib/puppet/parser/functions/chop.rb index 636b990..f242af3 100644 --- a/lib/puppet/parser/functions/chop.rb +++ b/lib/puppet/parser/functions/chop.rb @@ -4,9 +4,9 @@ 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 + 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 diff --git a/lib/puppet/parser/functions/concat.rb b/lib/puppet/parser/functions/concat.rb new file mode 100644 index 0000000..c86aa00 --- /dev/null +++ b/lib/puppet/parser/functions/concat.rb @@ -0,0 +1,37 @@ +# +# concat.rb +# + +module Puppet::Parser::Functions + newfunction(:concat, :type => :rvalue, :doc => <<-EOS +Appends the contents of array 2 onto array 1. + +*Example:* + + concat(['1','2','3'],['4','5','6']) + +Would result in: + + ['1','2','3','4','5','6'] + EOS + ) do |arguments| + + # Check that 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] + b = arguments[1] + + # Check that both args are arrays. + unless a.is_a?(Array) and b.is_a?(Array) + raise(Puppet::ParseError, 'concat(): Requires array to work with') + end + + result = a.concat(b) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/count.rb b/lib/puppet/parser/functions/count.rb new file mode 100644 index 0000000..52de1b8 --- /dev/null +++ b/lib/puppet/parser/functions/count.rb @@ -0,0 +1,22 @@ +module Puppet::Parser::Functions + newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS +Takes an array as first argument and an optional second argument. +Count the number of elements in array that matches second argument. +If called with only an array it counts the number of elements that are not nil/undef. + EOS + ) do |args| + + if (args.size > 2) then + raise(ArgumentError, "count(): Wrong number of arguments "+ + "given #{args.size} for 1 or 2.") + end + + collection, item = args + + if item then + collection.count item + else + collection.count { |obj| obj != nil && obj != :undef && obj != '' } + end + end +end diff --git a/lib/puppet/parser/functions/deep_merge.rb b/lib/puppet/parser/functions/deep_merge.rb new file mode 100644 index 0000000..6df32e9 --- /dev/null +++ b/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/lib/puppet/parser/functions/delete.rb b/lib/puppet/parser/functions/delete.rb index f814344..d03a293 100644 --- a/lib/puppet/parser/functions/delete.rb +++ b/lib/puppet/parser/functions/delete.rb @@ -27,7 +27,7 @@ string, or key from a hash. "given #{arguments.size} for 2.") end - collection = arguments[0] + collection = arguments[0].dup item = arguments[1] case collection diff --git a/lib/puppet/parser/functions/delete_undef_values.rb b/lib/puppet/parser/functions/delete_undef_values.rb new file mode 100644 index 0000000..f94d4da --- /dev/null +++ b/lib/puppet/parser/functions/delete_undef_values.rb @@ -0,0 +1,34 @@ +module Puppet::Parser::Functions + newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS +Returns a copy of input hash or array with all undefs deleted. + +*Examples:* + + $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false}) + +Would return: {a => 'A', b => '', d => false} + + $array = delete_undef_values(['A','',undef,false]) + +Would return: ['A','',false] + + EOS + ) do |args| + + raise(Puppet::ParseError, + "delete_undef_values(): Wrong number of arguments given " + + "(#{args.size})") if args.size < 1 + + unless args[0].is_a? Array or args[0].is_a? Hash + raise(Puppet::ParseError, + "delete_undef_values(): expected an array or hash, got #{args[0]} type #{args[0].class} ") + end + result = args[0].dup + if result.is_a?(Hash) + result.delete_if {|key, val| val.equal? :undef} + elsif result.is_a?(Array) + result.delete :undef + end + result + end +end diff --git a/lib/puppet/parser/functions/delete_values.rb b/lib/puppet/parser/functions/delete_values.rb new file mode 100644 index 0000000..f6c8c0e --- /dev/null +++ b/lib/puppet/parser/functions/delete_values.rb @@ -0,0 +1,26 @@ +module Puppet::Parser::Functions + newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS +Deletes all instances of a given value from a hash. + +*Examples:* + + delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B') + +Would return: {'a'=>'A','c'=>'C','B'=>'D'} + + EOS + ) do |arguments| + + raise(Puppet::ParseError, + "delete_values(): Wrong number of arguments given " + + "(#{arguments.size} of 2)") if arguments.size != 2 + + hash, item = arguments + + if not hash.is_a?(Hash) + raise(TypeError, "delete_values(): First argument must be a Hash. " + \ + "Given an argument of class #{hash.class}.") + end + hash.dup.delete_if { |key, val| item == val } + end +end diff --git a/lib/puppet/parser/functions/difference.rb b/lib/puppet/parser/functions/difference.rb new file mode 100644 index 0000000..cd258f7 --- /dev/null +++ b/lib/puppet/parser/functions/difference.rb @@ -0,0 +1,36 @@ +# +# difference.rb +# + +module Puppet::Parser::Functions + newfunction(:difference, :type => :rvalue, :doc => <<-EOS +This function returns the difference between two arrays. +The returned array is a copy of the original array, removing any items that +also appear in the second array. + +*Examples:* + + difference(["a","b","c"],["b","c","d"]) + +Would return: ["a"] + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "difference(): Wrong number of arguments " + + "given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'difference(): Requires 2 arrays') + end + + result = first - second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/dirname.rb b/lib/puppet/parser/functions/dirname.rb new file mode 100644 index 0000000..ea8cc1e --- /dev/null +++ b/lib/puppet/parser/functions/dirname.rb @@ -0,0 +1,15 @@ +module Puppet::Parser::Functions + newfunction(:dirname, :type => :rvalue, :doc => <<-EOS + Returns the dirname of a path. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "dirname(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size < 1 + + path = arguments[0] + return File.dirname(path) + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/ensure_packages.rb b/lib/puppet/parser/functions/ensure_packages.rb index 450ea02..1e0f225 100644 --- a/lib/puppet/parser/functions/ensure_packages.rb +++ b/lib/puppet/parser/functions/ensure_packages.rb @@ -1,7 +1,6 @@ # # ensure_packages.rb # -require 'puppet/parser/functions' module Puppet::Parser::Functions newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS @@ -9,13 +8,15 @@ Takes a list of packages and only installs them if they don't already exist. EOS ) do |arguments| - raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments " + - "given (#{arguments.size} for 1)") if arguments.size != 1 - raise(Puppet::ParseError, "ensure_packages(): Requires array " + - "given (#{arguments[0].class})") if !arguments[0].kind_of?(Array) + if arguments.size != 1 + raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") + end + + packages = Array(arguments[0]) Puppet::Parser::Functions.function(:ensure_resource) - arguments[0].each { |package_name| + packages.each { |package_name| function_ensure_resource(['package', package_name, {'ensure' => 'present' } ]) } end diff --git a/lib/puppet/parser/functions/ensure_resource.rb b/lib/puppet/parser/functions/ensure_resource.rb index fba2035..05e5593 100644 --- a/lib/puppet/parser/functions/ensure_resource.rb +++ b/lib/puppet/parser/functions/ensure_resource.rb @@ -13,23 +13,33 @@ resource. This example only creates the resource if it does not already exist: - ensure_resource('user, 'dan', {'ensure' => 'present' }) + 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 ||= {} - Puppet::Parser::Functions.function(:defined_with_params) - if function_defined_with_params(["#{type}[#{title}]", params]) - Puppet.debug("Resource #{type}[#{title}] not created b/c it already exists") - else - Puppet::Parser::Functions.function(:create_resources) - function_create_resources([type.capitalize, { 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}] not created because it already exists") + else + Puppet::Parser::Functions.function(:create_resources) + function_create_resources([type.capitalize, { item => params }]) + end end end diff --git a/lib/puppet/parser/functions/floor.rb b/lib/puppet/parser/functions/floor.rb new file mode 100644 index 0000000..a401923 --- /dev/null +++ b/lib/puppet/parser/functions/floor.rb @@ -0,0 +1,20 @@ +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 + + arg = arguments[0] + + 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/lib/puppet/parser/functions/getparam.rb b/lib/puppet/parser/functions/getparam.rb new file mode 100644 index 0000000..6d51006 --- /dev/null +++ b/lib/puppet/parser/functions/getparam.rb @@ -0,0 +1,35 @@ +# Test whether a given class or definition is defined +require 'puppet/parser/functions' + +Puppet::Parser::Functions.newfunction(:getparam, + :type => :rvalue, + :doc => <<-'ENDOFDOC' +Takes a resource reference and name of the parameter and +returns value of resource's parameter. + +*Examples:* + + define example_resource($param) { + } + + example_resource { "example_resource_instance": + param => "param_value" + } + + getparam(Example_resource["example_resource_instance"], "param") + +Would return: param_value +ENDOFDOC +) do |vals| + reference, param = vals + raise(ArgumentError, 'Must specify a reference') unless reference + raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String + + return '' if param.empty? + + if resource = findresource(reference.to_s) + return resource[param] if resource[param] + end + + return '' +end diff --git a/lib/puppet/parser/functions/hash.rb b/lib/puppet/parser/functions/hash.rb index 453ba1e..8cc4823 100644 --- a/lib/puppet/parser/functions/hash.rb +++ b/lib/puppet/parser/functions/hash.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:hash, :type => :rvalue, :doc => <<-EOS -This function converts and array into a hash. +This function converts an array into a hash. *Examples:* diff --git a/lib/puppet/parser/functions/intersection.rb b/lib/puppet/parser/functions/intersection.rb new file mode 100644 index 0000000..48f02e9 --- /dev/null +++ b/lib/puppet/parser/functions/intersection.rb @@ -0,0 +1,34 @@ +# +# intersection.rb +# + +module Puppet::Parser::Functions + newfunction(:intersection, :type => :rvalue, :doc => <<-EOS +This function returns an array an intersection of two. + +*Examples:* + + intersection(["a","b","c"],["b","c","d"]) + +Would return: ["b","c"] + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "intersection(): Wrong number of arguments " + + "given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'intersection(): Requires 2 arrays') + end + + result = first & second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_bool.rb b/lib/puppet/parser/functions/is_bool.rb new file mode 100644 index 0000000..8bbdbc8 --- /dev/null +++ b/lib/puppet/parser/functions/is_bool.rb @@ -0,0 +1,22 @@ +# +# is_bool.rb +# + +module Puppet::Parser::Functions + newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS +Returns true if the variable passed to this function is a boolean. + EOS + ) do |arguments| + + raise(Puppet::ParseError, "is_bool(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size != 1 + + type = arguments[0] + + result = type.is_a?(TrueClass) || type.is_a?(FalseClass) + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/is_function_available.rb b/lib/puppet/parser/functions/is_function_available.rb new file mode 100644 index 0000000..6cbd35c --- /dev/null +++ b/lib/puppet/parser/functions/is_function_available.rb @@ -0,0 +1,23 @@ +# +# 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 + + 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/lib/puppet/parser/functions/is_integer.rb b/lib/puppet/parser/functions/is_integer.rb index 6b29e98..c03d28d 100644 --- a/lib/puppet/parser/functions/is_integer.rb +++ b/lib/puppet/parser/functions/is_integer.rb @@ -4,7 +4,12 @@ module Puppet::Parser::Functions newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS -Returns true if the variable returned to this string is an integer. +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| @@ -15,12 +20,25 @@ Returns true if the variable returned to this string is an integer. value = arguments[0] - if value != value.to_i.to_s and !value.is_a? Fixnum then - return false - else + # 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 diff --git a/lib/puppet/parser/functions/is_ip_address.rb b/lib/puppet/parser/functions/is_ip_address.rb index b4a9a15..a90adab 100644 --- a/lib/puppet/parser/functions/is_ip_address.rb +++ b/lib/puppet/parser/functions/is_ip_address.rb @@ -15,7 +15,7 @@ Returns true if the string passed to this function is a valid IP address. "given #{arguments.size} for 1") end - begin + begin ip = IPAddr.new(arguments[0]) rescue ArgumentError return false diff --git a/lib/puppet/parser/functions/is_numeric.rb b/lib/puppet/parser/functions/is_numeric.rb index abf0321..e7e1d2a 100644 --- a/lib/puppet/parser/functions/is_numeric.rb +++ b/lib/puppet/parser/functions/is_numeric.rb @@ -4,7 +4,23 @@ module Puppet::Parser::Functions newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS -Returns true if the variable passed to this function is a number. +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| @@ -15,12 +31,44 @@ Returns true if the variable passed to this function is a number. value = arguments[0] - if value == value.to_f.to_s or value == value.to_i.to_s or value.is_a? Numeric then + # 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/lib/puppet/parser/functions/join.rb b/lib/puppet/parser/functions/join.rb index 005a46e..6c0a6ba 100644 --- a/lib/puppet/parser/functions/join.rb +++ b/lib/puppet/parser/functions/join.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:join, :type => :rvalue, :doc => <<-EOS -This function joins an array into a string using a seperator. +This function joins an array into a string using a separator. *Examples:* diff --git a/lib/puppet/parser/functions/merge.rb b/lib/puppet/parser/functions/merge.rb index 6ec085e..1b39f20 100644 --- a/lib/puppet/parser/functions/merge.rb +++ b/lib/puppet/parser/functions/merge.rb @@ -22,6 +22,7 @@ module Puppet::Parser::Functions 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 diff --git a/lib/puppet/parser/functions/num2bool.rb b/lib/puppet/parser/functions/num2bool.rb index 874db22..af0e6ed 100644 --- a/lib/puppet/parser/functions/num2bool.rb +++ b/lib/puppet/parser/functions/num2bool.rb @@ -2,38 +2,41 @@ # num2bool.rb # -# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ... - module Puppet::Parser::Functions newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS -This function converts a number into a true boolean. Zero becomes false. Numbers -higher then 0 become true. +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 + "given (#{arguments.size} for 1)") if arguments.size != 1 number = arguments[0] - # Only numbers allowed ... - unless number.match(/^\-?\d+$/) - raise(Puppet::ParseError, 'num2bool(): Requires integer to work with') + 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 - result = case number - when /^0$/ - false - when /^\-?\d+$/ - # Numbers in Puppet are often string-encoded which is troublesome ... - number = number.to_i - # We yield true for any positive number and false otherwise ... - number > 0 ? true : false - else - raise(Puppet::ParseError, 'num2bool(): Unknown numeric format given') - end + # Truncate Floats + number = number.to_i - return result + # Return true for any positive number and false otherwise + return number > 0 end end diff --git a/lib/puppet/parser/functions/parseyaml.rb b/lib/puppet/parser/functions/parseyaml.rb index e8ac8a4..53d54fa 100644 --- a/lib/puppet/parser/functions/parseyaml.rb +++ b/lib/puppet/parser/functions/parseyaml.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS -This function accepts YAML as a string and converts it into the correct +This function accepts YAML as a string and converts it into the correct Puppet structure. EOS ) do |arguments| diff --git a/lib/puppet/parser/functions/pick.rb b/lib/puppet/parser/functions/pick.rb index cbc0300..fdd0aef 100644 --- a/lib/puppet/parser/functions/pick.rb +++ b/lib/puppet/parser/functions/pick.rb @@ -21,7 +21,7 @@ EOS args.delete(:undefined) args.delete("") if args[0].to_s.empty? then - fail "Must provide non empty value." + fail Puppet::ParseError, "pick(): must receive at least one non empty value" else return args[0] end diff --git a/lib/puppet/parser/functions/pick_default.rb b/lib/puppet/parser/functions/pick_default.rb new file mode 100644 index 0000000..36e33ab --- /dev/null +++ b/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/lib/puppet/parser/functions/prefix.rb b/lib/puppet/parser/functions/prefix.rb index 4593976..d02286a 100644 --- a/lib/puppet/parser/functions/prefix.rb +++ b/lib/puppet/parser/functions/prefix.rb @@ -6,7 +6,7 @@ module Puppet::Parser::Functions newfunction(:prefix, :type => :rvalue, :doc => <<-EOS This function applies a prefix to all elements in an array. -*Examles:* +*Examples:* prefix(['a','b','c'], 'p') @@ -21,14 +21,14 @@ Will return: ['pa','pb','pc'] array = arguments[0] unless array.is_a?(Array) - raise(Puppet::ParseError, 'prefix(): Requires array to work with') + raise Puppet::ParseError, "prefix(): expected first argument to be an Array, got #{array.inspect}" end prefix = arguments[1] if arguments[1] if prefix unless prefix.is_a?(String) - raise(Puppet::ParseError, 'prefix(): Requires string to work with') + raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}" end end diff --git a/lib/puppet/parser/functions/range.rb b/lib/puppet/parser/functions/range.rb index 825617b..ffbdf84 100644 --- a/lib/puppet/parser/functions/range.rb +++ b/lib/puppet/parser/functions/range.rb @@ -27,6 +27,13 @@ Will return: ["a","b","c"] range("host01", "host10") Will return: ["host01", "host02", ..., "host09", "host10"] + +Passing a third argument will cause the generated range to step by that +interval, e.g. + + range("0", "9", "2") + +Will return: [0,2,4,6,8] EOS ) do |arguments| @@ -37,6 +44,7 @@ Will return: ["host01", "host02", ..., "host09", "host10"] if arguments.size > 1 start = arguments[0] stop = arguments[1] + step = arguments[2].nil? ? 1 : arguments[2].to_i.abs type = '..' # We select simplest type for Range available in Ruby ... @@ -71,7 +79,7 @@ Will return: ["host01", "host02", ..., "host09", "host10"] when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ... end - result = range.collect { |i| i } # Get them all ... Pokemon ... + result = range.step(step).collect { |i| i } # Get them all ... Pokemon ... return result end diff --git a/lib/puppet/parser/functions/squeeze.rb b/lib/puppet/parser/functions/squeeze.rb index 65c174a..81fadfd 100644 --- a/lib/puppet/parser/functions/squeeze.rb +++ b/lib/puppet/parser/functions/squeeze.rb @@ -14,9 +14,9 @@ Returns a new string where runs of the same character that occur in this set are end item = arguments[0] - squeezeval = arguments[1] + squeezeval = arguments[1] - if item.is_a?(Array) then + if item.is_a?(Array) then if squeezeval then item.collect { |i| i.squeeze(squeezeval) } else diff --git a/lib/puppet/parser/functions/str2bool.rb b/lib/puppet/parser/functions/str2bool.rb index 9ea6dd5..446732e 100644 --- a/lib/puppet/parser/functions/str2bool.rb +++ b/lib/puppet/parser/functions/str2bool.rb @@ -4,7 +4,7 @@ module Puppet::Parser::Functions newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS -This converts a string to a boolean. This attempt to convert strings that +This converts a string to a boolean. This attempt to convert strings that contain things like: y, 1, t, true to 'true' and strings that contain things like: 0, f, n, false, no to 'false'. EOS @@ -14,7 +14,7 @@ like: 0, f, n, false, no to 'false'. "given (#{arguments.size} for 1)") if arguments.size < 1 string = arguments[0] - + # If string is already Boolean, return it if !!string == string return string diff --git a/lib/puppet/parser/functions/suffix.rb b/lib/puppet/parser/functions/suffix.rb new file mode 100644 index 0000000..f7792d6 --- /dev/null +++ b/lib/puppet/parser/functions/suffix.rb @@ -0,0 +1,45 @@ +# +# suffix.rb +# + +module Puppet::Parser::Functions + newfunction(:suffix, :type => :rvalue, :doc => <<-EOS +This function applies a suffix to all elements in an array. + +*Examples:* + + suffix(['a','b','c'], 'p') + +Will return: ['ap','bp','cp'] + EOS + ) do |arguments| + + # Technically we support two arguments but only first is mandatory ... + raise(Puppet::ParseError, "suffix(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size < 1 + + array = arguments[0] + + unless array.is_a?(Array) + raise Puppet::ParseError, "suffix(): expected first argument to be an Array, got #{array.inspect}" + end + + suffix = arguments[1] if arguments[1] + + if suffix + unless suffix.is_a? String + raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}" + end + end + + # Turn everything into string same as join would do ... + result = array.collect do |i| + i = i.to_s + suffix ? i + suffix : i + end + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/union.rb b/lib/puppet/parser/functions/union.rb new file mode 100644 index 0000000..c91bb80 --- /dev/null +++ b/lib/puppet/parser/functions/union.rb @@ -0,0 +1,34 @@ +# +# union.rb +# + +module Puppet::Parser::Functions + newfunction(:union, :type => :rvalue, :doc => <<-EOS +This function returns a union of two arrays. + +*Examples:* + + union(["a","b","c"],["b","c","d"]) + +Would return: ["a","b","c","d"] + EOS + ) do |arguments| + + # Two arguments are required + raise(Puppet::ParseError, "union(): Wrong number of arguments " + + "given (#{arguments.size} for 2)") if arguments.size != 2 + + first = arguments[0] + second = arguments[1] + + unless first.is_a?(Array) && second.is_a?(Array) + raise(Puppet::ParseError, 'union(): Requires 2 arrays') + end + + result = first | second + + return result + end +end + +# vim: set ts=2 sw=2 et : diff --git a/lib/puppet/parser/functions/uriescape.rb b/lib/puppet/parser/functions/uriescape.rb index 67b93a6..0d81de5 100644 --- a/lib/puppet/parser/functions/uriescape.rb +++ b/lib/puppet/parser/functions/uriescape.rb @@ -15,7 +15,6 @@ module Puppet::Parser::Functions value = arguments[0] klass = value.class - unsafe = ":/?#[]@!$&'()*+,;= " unless [Array, String].include?(klass) raise(Puppet::ParseError, 'uriescape(): Requires either ' + @@ -26,7 +25,7 @@ module Puppet::Parser::Functions # Numbers in Puppet are often string-encoded which is troublesome ... result = value.collect { |i| i.is_a?(String) ? URI.escape(i,unsafe) : i } else - result = URI.escape(value,unsafe) + result = URI.escape(value) end return result diff --git a/lib/puppet/parser/functions/validate_augeas.rb b/lib/puppet/parser/functions/validate_augeas.rb new file mode 100644 index 0000000..154d660 --- /dev/null +++ b/lib/puppet/parser/functions/validate_augeas.rb @@ -0,0 +1,81 @@ +module Puppet::Parser::Functions + newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args| + Perform validation of a string using an Augeas lens + The first argument of this function should be a string to + test, and the second argument should be the name of the Augeas lens to use. + If Augeas fails to parse the string with the lens, the compilation will + abort with a parse error. + + A third argument can be specified, listing paths which should + not be found in the file. The `$file` variable points to the location + of the temporary file being tested in the Augeas tree. + + For example, if you want to make sure your passwd content never contains + a user `foo`, you could write: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo']) + + Or if you wanted to ensure that no users used the '/bin/barsh' shell, + you could use: + + validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]'] + + If a fourth argument is specified, this will be the error message raised and + seen by the user. + + A helpful error message can be returned like this: + + validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas') + + ENDHEREDOC + unless Puppet.features.augeas? + raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Augeas#Pre-requisites for how to activate it.") + end + + if (args.length < 2) or (args.length > 4) then + raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)") + end + + msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}" + + require 'augeas' + aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) + begin + content = args[0] + + # Test content in a temporary file + tmpfile = Tempfile.new("validate_augeas") + begin + tmpfile.write(content) + ensure + tmpfile.close + end + + # Check for syntax + lens = args[1] + aug.transform( + :lens => lens, + :name => 'Validate_augeas', + :incl => tmpfile.path + ) + aug.load! + + unless aug.match("/augeas/files#{tmpfile.path}//error").empty? + error = aug.get("/augeas/files#{tmpfile.path}//error/message") + msg += " with error: #{error}" + raise Puppet::ParseError, (msg) + end + + # Launch unit tests + tests = args[2] || [] + aug.defvar('file', "/files#{tmpfile.path}") + tests.each do |t| + msg += " testing path #{t}" + raise Puppet::ParseError, (msg) unless aug.match(t).empty? + end + ensure + aug.close + tmpfile.unlink + end + end +end diff --git a/lib/puppet/parser/functions/validate_bool.rb b/lib/puppet/parser/functions/validate_bool.rb index 62c1d88..59a0805 100644 --- a/lib/puppet/parser/functions/validate_bool.rb +++ b/lib/puppet/parser/functions/validate_bool.rb @@ -24,7 +24,7 @@ module Puppet::Parser::Functions end args.each do |arg| - unless (arg.is_a?(TrueClass) || arg.is_a?(FalseClass)) + unless function_is_bool([arg]) raise Puppet::ParseError, ("#{arg.inspect} is not a boolean. It looks to be a #{arg.class}") end end diff --git a/lib/puppet/parser/functions/validate_cmd.rb b/lib/puppet/parser/functions/validate_cmd.rb new file mode 100644 index 0000000..2ebe91c --- /dev/null +++ b/lib/puppet/parser/functions/validate_cmd.rb @@ -0,0 +1,48 @@ +require 'puppet/util/execution' + +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 file as last argument. 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: + + validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers 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 Puppet::Util::Execution.respond_to?('execute') + Puppet::Util::Execution.execute("#{checkscript} #{tmpfile.path}") + else + Puppet::Util.execute("#{checkscript} #{tmpfile.path}") + end + rescue Puppet::ExecutionFailure => detail + msg += "\n#{detail}" + raise Puppet::ParseError, msg + ensure + tmpfile.unlink + end + end +end diff --git a/lib/puppet/parser/functions/validate_ipv4_address.rb b/lib/puppet/parser/functions/validate_ipv4_address.rb new file mode 100644 index 0000000..fc02748 --- /dev/null +++ b/lib/puppet/parser/functions/validate_ipv4_address.rb @@ -0,0 +1,48 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv4 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "1.2.3.4" + validate_ipv4_address($my_ip) + validate_bool("8.8.8.8", "172.16.0.1", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ] + validate_ipv4_address($some_array) + + ENDHEREDOC + ) do |args| + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv4? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address." + end + end + + end + +end diff --git a/lib/puppet/parser/functions/validate_ipv6_address.rb b/lib/puppet/parser/functions/validate_ipv6_address.rb new file mode 100644 index 0000000..b0f2558 --- /dev/null +++ b/lib/puppet/parser/functions/validate_ipv6_address.rb @@ -0,0 +1,49 @@ +module Puppet::Parser::Functions + + newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC + Validate that all values passed are valid IPv6 addresses. + Fail compilation if any value fails this check. + + The following values will pass: + + $my_ip = "3ffe:505:2" + validate_ipv6_address(1) + validate_ipv6_address($my_ip) + validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip) + + The following values will fail, causing compilation to abort: + + $some_array = [ true, false, "garbage string", "1.2.3.4" ] + validate_ipv6_address($some_array) + + ENDHEREDOC + ) do |args| + + require "ipaddr" + rescuable_exceptions = [ ArgumentError ] + + if defined?(IPAddr::InvalidAddressError) + rescuable_exceptions << IPAddr::InvalidAddressError + end + + unless args.length > 0 then + raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)") + end + + args.each do |arg| + unless arg.is_a?(String) + raise Puppet::ParseError, "#{arg.inspect} is not a string." + end + + begin + unless IPAddr.new(arg).ipv6? + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + rescue *rescuable_exceptions + raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address." + end + end + + end + +end diff --git a/lib/puppet/parser/functions/validate_slength.rb b/lib/puppet/parser/functions/validate_slength.rb index fdcc0a2..7d534f3 100644 --- a/lib/puppet/parser/functions/validate_slength.rb +++ b/lib/puppet/parser/functions/validate_slength.rb @@ -2,51 +2,70 @@ 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. It fails if the first - argument is not a string or array of strings, and if arg 2 is not convertable - to a number. + less/equal to than the length of the second argument. An optional third + parameter can be given a the minimum length. It fails if the first + argument is not a string or array of strings, and if arg 2 and arg 3 are + not convertable to a number. The following values will pass: validate_slength("discombobulate",17) validate_slength(["discombobulate","moo"],17) + validate_slength(["discombobulate","moo"],17,3) The following valueis will not: validate_slength("discombobulate",1) validate_slength(["discombobulate","thermometer"],5) + validate_slength(["discombobulate","moo"],17,10) ENDHEREDOC - raise Puppet::ParseError, ("validate_slength(): Wrong number of arguments (#{args.length}; must be = 2)") unless args.length == 2 + raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3 - unless (args[0].is_a?(String) or args[0].is_a?(Array)) - raise Puppet::ParseError, ("validate_slength(): please pass a string, or an array of strings - what you passed didn't work for me at all - #{args[0].class}") - end + input, max_length, min_length = *args begin - max_length = args[1].to_i - rescue NoMethodError => e - raise Puppet::ParseError, ("validate_slength(): Couldn't convert whatever you passed as the length parameter to an integer - sorry: " + e.message ) + max_length = Integer(max_length) + raise ArgumentError if max_length <= 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}" + end + + if min_length + begin + min_length = Integer(min_length) + raise ArgumentError if min_length < 0 + rescue ArgumentError, TypeError + raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}" + end + else + min_length = 0 + end + + if min_length > max_length + raise Puppet::ParseError, "validate_slength(): Expected second argument to be larger than third argument" + end + + validator = lambda do |str| + unless str.length <= max_length and str.length >= min_length + raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}" + end end - raise Puppet::ParseError, ("validate_slength(): please pass a positive number as max_length") unless max_length > 0 - - case args[0] - when String - raise Puppet::ParseError, ("validate_slength(): #{args[0].inspect} is #{args[0].length} characters. It should have been less than or equal to #{max_length} characters") unless args[0].length <= max_length - when Array - args[0].each do |arg| - if arg.is_a?(String) - unless ( arg.is_a?(String) and arg.length <= max_length ) - raise Puppet::ParseError, ("validate_slength(): #{arg.inspect} is #{arg.length} characters. It should have been less than or equal to #{max_length} characters") - end - else - raise Puppet::ParseError, ("validate_slength(): #{arg.inspect} is not a string, it's a #{arg.class}") - 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 - else - raise Puppet::ParseError, ("validate_slength(): please pass a string, or an array of strings - what you passed didn't work for me at all - #{args[0].class}") + 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/lib/puppet/provider/file_line/ruby.rb b/lib/puppet/provider/file_line/ruby.rb index a3219d3..94e7fac 100644 --- a/lib/puppet/provider/file_line/ruby.rb +++ b/lib/puppet/provider/file_line/ruby.rb @@ -1,6 +1,4 @@ - Puppet::Type.type(:file_line).provide(:ruby) do - def exists? lines.find do |line| line.chomp == resource[:line].chomp @@ -9,9 +7,11 @@ Puppet::Type.type(:file_line).provide(:ruby) do def create if resource[:match] - handle_create_with_match() + handle_create_with_match + elsif resource[:after] + handle_create_with_after else - handle_create_without_match() + append_line end end @@ -35,8 +35,8 @@ Puppet::Type.type(:file_line).provide(:ruby) do def handle_create_with_match() regex = resource[:match] ? Regexp.new(resource[:match]) : nil match_count = lines.select { |l| regex.match(l) }.size - if match_count > 1 - raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'" + 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| @@ -49,11 +49,35 @@ Puppet::Type.type(:file_line).provide(:ruby) do end end - def handle_create_without_match + def handle_create_with_after + regex = Regexp.new(resource[:after]) + + count = lines.count {|l| l.match(regex)} + + case count + when 1 # find the line to put our line after + File.open(resource[:path], 'w') do |fh| + lines.each do |l| + fh.puts(l) + if regex.match(l) then + fh.puts(resource[:line]) + end + end + end + when 0 # append the line to the end of the file + append_line + else + raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'. One or no line must match the pattern." + end + end + + ## + # append the line to the file. + # + # @api private + def append_line File.open(resource[:path], 'a') do |fh| fh.puts resource[:line] end end - - end diff --git a/lib/puppet/type/anchor.rb b/lib/puppet/type/anchor.rb index 6b81732..fe1e5aa 100644 --- a/lib/puppet/type/anchor.rb +++ b/lib/puppet/type/anchor.rb @@ -38,4 +38,9 @@ Puppet::Type.newtype(:anchor) 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/lib/puppet/type/file_line.rb b/lib/puppet/type/file_line.rb index f71a4bc..323fc4c 100644 --- a/lib/puppet/type/file_line.rb +++ b/lib/puppet/type/file_line.rb @@ -37,6 +37,15 @@ Puppet::Type.newtype(:file_line) do 'if a match is found, we replace that line rather than adding a new line.' end + newparam(:multiple) do + desc 'An optional value to determine if match can change multiple lines.' + newvalues(true, false) + end + + newparam(:after) do + desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)' + end + newparam(:line) do desc 'The line to be appended to the file located by the path parameter.' end diff --git a/spec/acceptance/nodesets/centos-64-x64-pe.yml b/spec/acceptance/nodesets/centos-64-x64-pe.yml new file mode 100644 index 0000000..7d9242f --- /dev/null +++ b/spec/acceptance/nodesets/centos-64-x64-pe.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-64-x64: + roles: + - master + - database + - dashboard + platform: el-6-x86_64 + box : centos-64-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: pe diff --git a/spec/acceptance/nodesets/centos-64-x64.yml b/spec/acceptance/nodesets/centos-64-x64.yml new file mode 100644 index 0000000..05540ed --- /dev/null +++ b/spec/acceptance/nodesets/centos-64-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-64-x64: + roles: + - master + platform: el-6-x86_64 + box : centos-64-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/nodesets/centos-65-x64.yml b/spec/acceptance/nodesets/centos-65-x64.yml new file mode 100644 index 0000000..4e2cb80 --- /dev/null +++ b/spec/acceptance/nodesets/centos-65-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-65-x64: + roles: + - master + platform: el-6-x86_64 + box : centos-65-x64-vbox436-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/nodesets/default.yml b/spec/acceptance/nodesets/default.yml new file mode 120000 index 0000000..2719644 --- /dev/null +++ b/spec/acceptance/nodesets/default.yml @@ -0,0 +1 @@ +centos-64-x64.yml
\ No newline at end of file diff --git a/spec/acceptance/nodesets/fedora-18-x64.yml b/spec/acceptance/nodesets/fedora-18-x64.yml new file mode 100644 index 0000000..1361649 --- /dev/null +++ b/spec/acceptance/nodesets/fedora-18-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + fedora-18-x64: + roles: + - master + platform: fedora-18-x86_64 + box : fedora-18-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/fedora-18-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/nodesets/sles-11-x64.yml b/spec/acceptance/nodesets/sles-11-x64.yml new file mode 100644 index 0000000..41abe21 --- /dev/null +++ b/spec/acceptance/nodesets/sles-11-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + sles-11-x64.local: + roles: + - master + platform: sles-11-x64 + box : sles-11sp1-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/sles-11sp1-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml new file mode 100644 index 0000000..5ca1514 --- /dev/null +++ b/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-server-10044-x64: + roles: + - master + platform: ubuntu-10.04-amd64 + box : ubuntu-server-10044-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml new file mode 100644 index 0000000..d065b30 --- /dev/null +++ b/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-server-12042-x64: + roles: + - master + platform: ubuntu-12.04-amd64 + box : ubuntu-server-12042-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/spec/acceptance/unsupported_spec.rb b/spec/acceptance/unsupported_spec.rb new file mode 100644 index 0000000..449f35a --- /dev/null +++ b/spec/acceptance/unsupported_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper_acceptance' + +describe 'unsupported distributions and OSes', :if => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do + it 'should fail' do + pp = <<-EOS + class { 'mysql::server': } + EOS + expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/unsupported osfamily/i) + end +end diff --git a/spec/classes/anchor_spec.rb b/spec/classes/anchor_spec.rb new file mode 100644 index 0000000..2e1fcba --- /dev/null +++ b/spec/classes/anchor_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' +require 'puppet_spec/compiler' + +describe "anchorrefresh" do + include PuppetSpec::Compiler + + let :transaction do + apply_compiled_manifest(<<-ANCHORCLASS) + class anchored { + anchor { 'anchored::begin': } + ~> anchor { 'anchored::end': } + } + + class anchorrefresh { + notify { 'first': } + ~> class { 'anchored': } + ~> anchor { 'final': } + } + + include anchorrefresh + ANCHORCLASS + end + + it 'propagates events through the anchored class' do + resource = transaction.resource_status('Anchor[final]') + + expect(resource.restarted).to eq(true) + end +end diff --git a/spec/fixtures/dscacheutil/root b/spec/fixtures/dscacheutil/root new file mode 100644 index 0000000..1e34519 --- /dev/null +++ b/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/spec/functions/ensure_packages_spec.rb b/spec/functions/ensure_packages_spec.rb index 4163d69..bf62eff 100644 --- a/spec/functions/ensure_packages_spec.rb +++ b/spec/functions/ensure_packages_spec.rb @@ -38,6 +38,10 @@ describe 'ensure_packages' do it 'accepts an array of values' do scope.function_ensure_packages([['foo']]) end + + it 'accepts a single package name as a string' do + scope.function_ensure_packages(['foo']) + end end context 'given a catalog with puppet package => absent' do diff --git a/spec/functions/ensure_resource_spec.rb b/spec/functions/ensure_resource_spec.rb index 430691c..459d917 100644 --- a/spec/functions/ensure_resource_spec.rb +++ b/spec/functions/ensure_resource_spec.rb @@ -58,4 +58,55 @@ describe 'ensure_resource' do it { expect { compile_to_catalog(pp) }.to raise_error } end + describe 'when an array of new resources are passed in' do + let :catalog do + compile_to_catalog("ensure_resource('User', ['dan', 'alex'], {})") + end + + it 'should contain the ensured resources' do + expect(catalog.resource('User[dan]').to_s).to eq('User[dan]') + expect(catalog.resource('User[alex]').to_s).to eq('User[alex]') + end + end + + describe 'when an array of existing resources is compared against existing resources' do + pp = <<-EOS + user { 'dan': ensure => present; 'alex': ensure => present } + ensure_resource('User', ['dan', 'alex'], {}) + EOS + + let :catalog do + compile_to_catalog(pp) + end + + it 'should return the existing resources' do + expect(catalog.resource('User[dan]').to_s).to eq('User[dan]') + expect(catalog.resource('User[alex]').to_s).to eq('User[alex]') + end + end + + describe 'works when compared against existing resources with attributes' do + [ + "ensure_resource('User', ['dan', 'alex'], {})", + "ensure_resource('User', ['dan', 'alex'], '')", + "ensure_resource('User', ['dan', 'alex'], {'ensure' => 'present'})", + ].each do |ensure_resource| + pp = <<-EOS + user { 'dan': ensure => present; 'alex': ensure => present } + #{ensure_resource} + EOS + + it { expect { compile_to_catalog(pp) }.to_not raise_error } + end + end + + describe 'fails when compared against existing resources with conflicting attributes' do + pp = <<-EOS + user { 'dan': ensure => present; 'alex': ensure => present } + ensure_resource('User', ['dan', 'alex'], {'ensure' => 'absent'}) + EOS + + it { expect { compile_to_catalog(pp) }.to raise_error } + end + end diff --git a/spec/functions/getparam_spec.rb b/spec/functions/getparam_spec.rb new file mode 100644 index 0000000..7f5ad1a --- /dev/null +++ b/spec/functions/getparam_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' +require 'rspec-puppet' +require 'puppet_spec/compiler' + +describe 'getparam' do + include PuppetSpec::Compiler + + before :each do + Puppet::Parser::Functions.autoloader.loadall + Puppet::Parser::Functions.function(:getparam) + end + + let :node do Puppet::Node.new('localhost') end + let :compiler do Puppet::Parser::Compiler.new(node) end + if Puppet.version.to_f >= 3.0 + let :scope do Puppet::Parser::Scope.new(compiler) end + else + let :scope do + newscope = Puppet::Parser::Scope.new + newscope.compiler = compiler + newscope.source = Puppet::Resource::Type.new(:node, :localhost) + newscope + end + end + + it "should exist" do + Puppet::Parser::Functions.function("getparam").should == "function_getparam" + end + + describe 'when a resource is not specified' do + it { expect { scope.function_getparam([]) }.to raise_error } + it { expect { scope.function_getparam(['User[dan]']) }.to raise_error } + it { expect { scope.function_getparam(['User[dan]']) }.to raise_error } + it { expect { scope.function_getparam(['User[dan]', {}]) }.to raise_error } + # This seems to be OK because we just check for a string. + it { expect { scope.function_getparam(['User[dan]', '']) }.to_not raise_error } + end + + describe 'when compared against a resource with no params' do + let :catalog do + compile_to_catalog(<<-EOS + user { "dan": } + EOS + ) + end + + it do + expect(scope.function_getparam(['User[dan]', 'shell'])).to eq('') + end + end + + describe 'when compared against a resource with params' do + let :catalog do + compile_to_catalog(<<-EOS + user { 'dan': ensure => present, shell => '/bin/sh', managehome => false} + $test = getparam(User[dan], 'shell') + EOS + ) + end + + it do + resource = Puppet::Parser::Resource.new(:user, 'dan', {:scope => scope}) + resource.set_parameter('ensure', 'present') + resource.set_parameter('shell', '/bin/sh') + resource.set_parameter('managehome', false) + compiler.add_resource(scope, resource) + + expect(scope.function_getparam(['User[dan]', 'shell'])).to eq('/bin/sh') + expect(scope.function_getparam(['User[dan]', ''])).to eq('') + expect(scope.function_getparam(['User[dan]', 'ensure'])).to eq('present') + # TODO: Expected this to be false, figure out why we're getting '' back. + expect(scope.function_getparam(['User[dan]', 'managehome'])).to eq('') + end + end +end diff --git a/spec/monkey_patches/publicize_methods.rb b/spec/monkey_patches/publicize_methods.rb index b39e9c0..f3a1abf 100755 --- a/spec/monkey_patches/publicize_methods.rb +++ b/spec/monkey_patches/publicize_methods.rb @@ -8,4 +8,3 @@ class Class self.class_eval { private(*saved_private_instance_methods) } end end - diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..4cefa75 --- /dev/null +++ b/spec/spec_helper_acceptance.rb @@ -0,0 +1,33 @@ +require 'beaker-rspec' + +UNSUPPORTED_PLATFORMS = [] + +unless ENV['RS_PROVISION'] == 'no' + hosts.each do |host| + # Install Puppet + if host.is_pe? + install_pe + else + install_package host, 'rubygems' + on host, 'gem install puppet --no-ri --no-rdoc' + on host, "mkdir -p #{host['distmoduledir']}" + end + end +end + +RSpec.configure do |c| + # Project root + proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + + # Readable test descriptions + c.formatter = :documentation + + # Configure all nodes in nodeset + c.before :suite do + # Install module and dependencies + puppet_module_install(:source => proj_root, :module_name => 'stdlib') + hosts.each do |host| + shell('/bin/touch /etc/puppet/hiera.yaml') + end + end +end diff --git a/spec/unit/facter/pe_required_facts_spec.rb b/spec/unit/facter/pe_required_facts_spec.rb new file mode 100644 index 0000000..85ff6ab --- /dev/null +++ b/spec/unit/facter/pe_required_facts_spec.rb @@ -0,0 +1,70 @@ +# Puppet Enterprise requires the following facts to be set in order to operate. +# These facts are set using the file ???? and the two facts are +# `fact_stomp_port`, and `fact_stomp_server`. +# + +require 'spec_helper' + +describe "External facts in /etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt" do + context "With Facter 1.6.17 which does not have external facts support" do + before :each do + Facter.stubs(:version).returns("1.6.17") + Facter::Util::Root.stubs(:root?).returns(true) + # Stub out the filesystem for stdlib + Dir.stubs(:entries).with("/etc/puppetlabs/facter/facts.d"). + returns(['puppet_enterprise_installer.txt']) + Dir.stubs(:entries).with("/etc/facter/facts.d").returns([]) + File.stubs(:readlines).with('/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt'). + returns([ + "fact_stomp_port=61613\n", + "fact_stomp_server=puppetmaster.acme.com\n", + "fact_is_puppetagent=true\n", + "fact_is_puppetmaster=false\n", + "fact_is_puppetca=false\n", + "fact_is_puppetconsole=false\n", + ]) + if Facter.collection.respond_to? :load + Facter.collection.load(:facter_dot_d) + else + Facter.collection.loader.load(:facter_dot_d) + end + end + + it 'defines fact_stomp_port' do + Facter.fact(:fact_stomp_port).value.should == '61613' + end + it 'defines fact_stomp_server' do + Facter.fact(:fact_stomp_server).value.should == 'puppetmaster.acme.com' + end + it 'defines fact_is_puppetagent' do + Facter.fact(:fact_is_puppetagent).value.should == 'true' + end + it 'defines fact_is_puppetmaster' do + Facter.fact(:fact_is_puppetmaster).value.should == 'false' + end + it 'defines fact_is_puppetca' do + Facter.fact(:fact_is_puppetca).value.should == 'false' + end + it 'defines fact_is_puppetconsole' do + Facter.fact(:fact_is_puppetconsole).value.should == 'false' + end + end + + [ '1.7.1', '2.0.1' ].each do |v| + context "With Facter #{v} which has external facts support" do + before :each do + Facter.stubs(:version).returns(v) + end + + it 'does not call Facter::Util::DotD.new' do + Facter::Util::DotD.expects(:new).never + + if Facter.collection.respond_to? :load + Facter.collection.load(:facter_dot_d) + else + Facter.collection.loader.load(:facter_dot_d) + end + end + end + end +end diff --git a/spec/unit/facter/root_home_spec.rb b/spec/unit/facter/root_home_spec.rb index ce80684..532fae1 100644 --- a/spec/unit/facter/root_home_spec.rb +++ b/spec/unit/facter/root_home_spec.rb @@ -20,15 +20,6 @@ describe Facter::Util::RootHome do Facter::Util::RootHome.get_root_home.should == expected_root_home end end - context "macosx" do - let(:root_ent) { "root:*:0:0:System Administrator:/var/root:/bin/sh" } - let(:expected_root_home) { "/var/root" } - - it "should return /var/root" do - Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent) - Facter::Util::RootHome.get_root_home.should == expected_root_home - end - end context "windows" do before :each do Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(nil) @@ -38,3 +29,23 @@ describe Facter::Util::RootHome do 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) + Facter.fact(:root_home).value.should == expected_root_home + end + end + +end diff --git a/spec/unit/puppet/parser/functions/any2array_spec.rb b/spec/unit/puppet/parser/functions/any2array_spec.rb new file mode 100644 index 0000000..b266e84 --- /dev/null +++ b/spec/unit/puppet/parser/functions/any2array_spec.rb @@ -0,0 +1,55 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the any2array function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("any2array").should == "function_any2array" + end + + it "should return an empty array if there is less than 1 argument" do + result = scope.function_any2array([]) + result.should(eq([])) + end + + it "should convert boolean true to [ true ] " do + result = scope.function_any2array([true]) + result.should(eq([true])) + end + + it "should convert one object to [object]" do + result = scope.function_any2array(['one']) + result.should(eq(['one'])) + end + + it "should convert multiple objects to [objects]" do + result = scope.function_any2array(['one', 'two']) + result.should(eq(['one', 'two'])) + end + + it "should return empty array it was called with" do + result = scope.function_any2array([[]]) + result.should(eq([])) + end + + it "should return one-member array it was called with" do + result = scope.function_any2array([['string']]) + result.should(eq(['string'])) + end + + it "should return multi-member array it was called with" do + result = scope.function_any2array([['one', 'two']]) + result.should(eq(['one', 'two'])) + end + + it "should return members of a hash it was called with" do + result = scope.function_any2array([{ 'key' => 'value' }]) + result.should(eq(['key', 'value'])) + end + + it "should return an empty array if it was called with an empty hash" do + result = scope.function_any2array([{ }]) + result.should(eq([])) + end +end diff --git a/spec/unit/puppet/parser/functions/base64_spec.rb b/spec/unit/puppet/parser/functions/base64_spec.rb new file mode 100755 index 0000000..5faa5e6 --- /dev/null +++ b/spec/unit/puppet/parser/functions/base64_spec.rb @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe "the base64 function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("base64").should == "function_base64" + end + + it "should raise a ParseError if there are other than 2 arguments" do + expect { scope.function_base64([]) }.to(raise_error(Puppet::ParseError)) + expect { scope.function_base64(["asdf"]) }.to(raise_error(Puppet::ParseError)) + expect { scope.function_base64(["asdf","moo","cow"]) }.to(raise_error(Puppet::ParseError)) + end + + it "should raise a ParseError if argument 1 isn't 'encode' or 'decode'" do + expect { scope.function_base64(["bees","astring"]) }.to(raise_error(Puppet::ParseError, /first argument must be one of/)) + end + + it "should raise a ParseError if argument 2 isn't a string" do + expect { scope.function_base64(["encode",["2"]]) }.to(raise_error(Puppet::ParseError, /second argument must be a string/)) + end + + it "should encode a encoded string" do + result = scope.function_base64(["encode",'thestring']) + result.should =~ /\AdGhlc3RyaW5n\n\Z/ + end + it "should decode a base64 encoded string" do + result = scope.function_base64(["decode",'dGhlc3RyaW5n']) + result.should == 'thestring' + end +end diff --git a/spec/unit/puppet/parser/functions/concat_spec.rb b/spec/unit/puppet/parser/functions/concat_spec.rb new file mode 100644 index 0000000..123188b --- /dev/null +++ b/spec/unit/puppet/parser/functions/concat_spec.rb @@ -0,0 +1,15 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the concat function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { scope.function_concat([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should be able to concat an array" do + result = scope.function_concat([['1','2','3'],['4','5','6']]) + result.should(eq(['1','2','3','4','5','6'])) + end +end diff --git a/spec/unit/puppet/parser/functions/count_spec.rb b/spec/unit/puppet/parser/functions/count_spec.rb new file mode 100644 index 0000000..2453815 --- /dev/null +++ b/spec/unit/puppet/parser/functions/count_spec.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe "the count function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("count").should == "function_count" + end + + it "should raise a ArgumentError if there is more than 2 arguments" do + lambda { scope.function_count(['foo', 'bar', 'baz']) }.should( raise_error(ArgumentError)) + end + + it "should be able to count arrays" do + scope.function_count([["1","2","3"]]).should(eq(3)) + end + + it "should be able to count matching elements in arrays" do + scope.function_count([["1", "2", "2"], "2"]).should(eq(2)) + end + + it "should not count nil or empty strings" do + scope.function_count([["foo","bar",nil,""]]).should(eq(2)) + end + + it 'does not count an undefined hash key or an out of bound array index (which are both :undef)' do + expect(scope.function_count([["foo",:undef,:undef]])).to eq(1) + end +end diff --git a/spec/unit/puppet/parser/functions/deep_merge_spec.rb b/spec/unit/puppet/parser/functions/deep_merge_spec.rb new file mode 100644 index 0000000..f134701 --- /dev/null +++ b/spec/unit/puppet/parser/functions/deep_merge_spec.rb @@ -0,0 +1,105 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:deep_merge) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe 'when calling deep_merge from puppet' do + it "should not compile when no arguments are passed" do + pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = '$x = deep_merge()' + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + + it "should not compile when 1 argument is passed" do + pending("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./ + Puppet[:code] = "$my_hash={'one' => 1}\n$x = deep_merge($my_hash)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end + + describe 'when calling deep_merge on the scope instance' do + it 'should require all parameters are hashes' do + expect { new_hash = scope.function_deep_merge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/) + expect { new_hash = scope.function_deep_merge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/) + end + + it 'should accept empty strings as puppet undef' do + expect { new_hash = scope.function_deep_merge([{}, ''])}.not_to raise_error + end + + it 'should be able to deep_merge two hashes' do + new_hash = scope.function_deep_merge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}]) + new_hash['one'].should == '1' + new_hash['two'].should == '2' + new_hash['three'].should == '2' + end + + it 'should deep_merge multiple hashes' do + hash = scope.function_deep_merge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}]) + hash['one'].should == '3' + end + + it 'should accept empty hashes' do + scope.function_deep_merge([{},{},{}]).should == {} + end + + it 'should deep_merge subhashes' do + hash = scope.function_deep_merge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }]) + hash['one'].should == 1 + hash['two'].should == 2 + hash['three'].should == { 'four' => 4 } + end + + it 'should append to subhashes' do + hash = scope.function_deep_merge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }]) + hash['one'].should == { 'two' => 2, 'three' => 3 } + end + + it 'should append to subhashes 2' do + hash = scope.function_deep_merge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }]) + hash['one'].should == 1 + hash['two'].should == 'dos' + hash['three'].should == { 'four' => 4, 'five' => 5 } + end + + it 'should append to subhashes 3' do + hash = scope.function_deep_merge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }]) + hash['key1'].should == { 'a' => 1, 'b' => 99 } + hash['key2'].should == { 'c' => 3 } + end + + it 'should not change the original hashes' do + hash1 = {'one' => { 'two' => 2 } } + hash2 = { 'one' => { 'three' => 3 } } + hash = scope.function_deep_merge([hash1, hash2]) + hash1.should == {'one' => { 'two' => 2 } } + hash2.should == { 'one' => { 'three' => 3 } } + hash['one'].should == { 'two' => 2, 'three' => 3 } + end + + it 'should not change the original hashes 2' do + hash1 = {'one' => { 'two' => [1,2] } } + hash2 = { 'one' => { 'three' => 3 } } + hash = scope.function_deep_merge([hash1, hash2]) + hash1.should == {'one' => { 'two' => [1,2] } } + hash2.should == { 'one' => { 'three' => 3 } } + hash['one'].should == { 'two' => [1,2], 'three' => 3 } + end + + it 'should not change the original hashes 3' do + hash1 = {'one' => { 'two' => [1,2, {'two' => 2} ] } } + hash2 = { 'one' => { 'three' => 3 } } + hash = scope.function_deep_merge([hash1, hash2]) + hash1.should == {'one' => { 'two' => [1,2, {'two' => 2}] } } + hash2.should == { 'one' => { 'three' => 3 } } + hash['one'].should == { 'two' => [1,2, {'two' => 2} ], 'three' => 3 } + hash['one']['two'].should == [1,2, {'two' => 2}] + end + end +end diff --git a/spec/unit/puppet/parser/functions/delete_at_spec.rb b/spec/unit/puppet/parser/functions/delete_at_spec.rb index d8d9618..593cf45 100755 --- a/spec/unit/puppet/parser/functions/delete_at_spec.rb +++ b/spec/unit/puppet/parser/functions/delete_at_spec.rb @@ -16,4 +16,10 @@ describe "the delete_at function" do result = scope.function_delete_at([['a','b','c'],1]) result.should(eq(['a','c'])) end + + it "should not change origin array passed as argument" do + origin_array = ['a','b','c','d'] + result = scope.function_delete_at([origin_array, 1]) + origin_array.should(eq(['a','b','c','d'])) + end end diff --git a/spec/unit/puppet/parser/functions/delete_spec.rb b/spec/unit/puppet/parser/functions/delete_spec.rb index 2f29c93..1508a63 100755 --- a/spec/unit/puppet/parser/functions/delete_spec.rb +++ b/spec/unit/puppet/parser/functions/delete_spec.rb @@ -35,4 +35,22 @@ describe "the delete function" do result.should(eq({ 'a' => 1, 'c' => 3 })) end + it "should not change origin array passed as argument" do + origin_array = ['a','b','c','d'] + result = scope.function_delete([origin_array, 'b']) + origin_array.should(eq(['a','b','c','d'])) + end + + it "should not change the origin string passed as argument" do + origin_string = 'foobarbabarz' + result = scope.function_delete([origin_string,'bar']) + origin_string.should(eq('foobarbabarz')) + end + + it "should not change origin hash passed as argument" do + origin_hash = { 'a' => 1, 'b' => 2, 'c' => 3 } + result = scope.function_delete([origin_hash, 'b']) + origin_hash.should(eq({ 'a' => 1, 'b' => 2, 'c' => 3 })) + end + end diff --git a/spec/unit/puppet/parser/functions/delete_undef_values_spec.rb b/spec/unit/puppet/parser/functions/delete_undef_values_spec.rb new file mode 100644 index 0000000..b341d88 --- /dev/null +++ b/spec/unit/puppet/parser/functions/delete_undef_values_spec.rb @@ -0,0 +1,41 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the delete_undef_values function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("delete_undef_values").should == "function_delete_undef_values" + end + + it "should raise a ParseError if there is less than 1 argument" do + lambda { scope.function_delete_undef_values([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should raise a ParseError if the argument is not Array nor Hash" do + lambda { scope.function_delete_undef_values(['']) }.should( raise_error(Puppet::ParseError)) + lambda { scope.function_delete_undef_values([nil]) }.should( raise_error(Puppet::ParseError)) + end + + it "should delete all undef items from Array and only these" do + result = scope.function_delete_undef_values([['a',:undef,'c','undef']]) + result.should(eq(['a','c','undef'])) + end + + it "should delete all undef items from Hash and only these" do + result = scope.function_delete_undef_values([{'a'=>'A','b'=>:undef,'c'=>'C','d'=>'undef'}]) + result.should(eq({'a'=>'A','c'=>'C','d'=>'undef'})) + end + + it "should not change origin array passed as argument" do + origin_array = ['a',:undef,'c','undef'] + result = scope.function_delete_undef_values([origin_array]) + origin_array.should(eq(['a',:undef,'c','undef'])) + end + + it "should not change origin hash passed as argument" do + origin_hash = { 'a' => 1, 'b' => :undef, 'c' => 'undef' } + result = scope.function_delete_undef_values([origin_hash]) + origin_hash.should(eq({ 'a' => 1, 'b' => :undef, 'c' => 'undef' })) + end +end diff --git a/spec/unit/puppet/parser/functions/delete_values_spec.rb b/spec/unit/puppet/parser/functions/delete_values_spec.rb new file mode 100644 index 0000000..8d7f231 --- /dev/null +++ b/spec/unit/puppet/parser/functions/delete_values_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the delete_values function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("delete_values").should == "function_delete_values" + end + + it "should raise a ParseError if there are fewer than 2 arguments" do + lambda { scope.function_delete_values([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should raise a ParseError if there are greater than 2 arguments" do + lambda { scope.function_delete_values([[], 'foo', 'bar']) }.should( raise_error(Puppet::ParseError)) + end + + it "should raise a TypeError if the argument is not a hash" do + lambda { scope.function_delete_values([1,'bar']) }.should( raise_error(TypeError)) + lambda { scope.function_delete_values(['foo','bar']) }.should( raise_error(TypeError)) + lambda { scope.function_delete_values([[],'bar']) }.should( raise_error(TypeError)) + end + + it "should delete all instances of a value from a hash" do + result = scope.function_delete_values([{ 'a'=>'A', 'b'=>'B', 'B'=>'C', 'd'=>'B' },'B']) + result.should(eq({ 'a'=>'A', 'B'=>'C' })) + end + + it "should not change origin hash passed as argument" do + origin_hash = { 'a' => 1, 'b' => 2, 'c' => 3 } + result = scope.function_delete_values([origin_hash, 2]) + origin_hash.should(eq({ 'a' => 1, 'b' => 2, 'c' => 3 })) + end + +end diff --git a/spec/unit/puppet/parser/functions/difference_spec.rb b/spec/unit/puppet/parser/functions/difference_spec.rb new file mode 100644 index 0000000..9feff09 --- /dev/null +++ b/spec/unit/puppet/parser/functions/difference_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the difference function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("difference").should == "function_difference" + end + + it "should raise a ParseError if there are fewer than 2 arguments" do + lambda { scope.function_difference([]) }.should( raise_error(Puppet::ParseError) ) + end + + it "should return the difference between two arrays" do + result = scope.function_difference([["a","b","c"],["b","c","d"]]) + result.should(eq(["a"])) + end +end diff --git a/spec/unit/puppet/parser/functions/dirname_spec.rb b/spec/unit/puppet/parser/functions/dirname_spec.rb new file mode 100755 index 0000000..fb3b4fe --- /dev/null +++ b/spec/unit/puppet/parser/functions/dirname_spec.rb @@ -0,0 +1,24 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the dirname function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("dirname").should == "function_dirname" + end + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { scope.function_dirname([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should return dirname for an absolute path" do + result = scope.function_dirname(['/path/to/a/file.ext']) + result.should(eq('/path/to/a')) + end + + it "should return dirname for a relative path" do + result = scope.function_dirname(['path/to/a/file.ext']) + result.should(eq('path/to/a')) + end +end diff --git a/spec/unit/puppet/parser/functions/floor_spec.rb b/spec/unit/puppet/parser/functions/floor_spec.rb new file mode 100644 index 0000000..dbc8c77 --- /dev/null +++ b/spec/unit/puppet/parser/functions/floor_spec.rb @@ -0,0 +1,39 @@ +#! /usr/bin/env ruby -S rspec + +require 'spec_helper' + +describe "the floor function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("floor").should == "function_floor" + end + + it "should raise a ParseError if there is less than 1 argument" do + lambda { scope.function_floor([]) }.should( raise_error(Puppet::ParseError, /Wrong number of arguments/)) + end + + it "should should raise a ParseError if input isn't numeric (eg. String)" do + lambda { scope.function_floor(["foo"]) }.should( raise_error(Puppet::ParseError, /Wrong argument type/)) + end + + it "should should raise a ParseError if input isn't numeric (eg. Boolean)" do + lambda { scope.function_floor([true]) }.should( raise_error(Puppet::ParseError, /Wrong argument type/)) + end + + it "should return an integer when a numeric type is passed" do + result = scope.function_floor([12.4]) + result.is_a?(Integer).should(eq(true)) + end + + it "should return the input when an integer is passed" do + result = scope.function_floor([7]) + result.should(eq(7)) + end + + it "should return the largest integer less than or equal to the input" do + result = scope.function_floor([3.8]) + result.should(eq(3)) + end +end + diff --git a/spec/unit/puppet/parser/functions/fqdn_rotate_spec.rb b/spec/unit/puppet/parser/functions/fqdn_rotate_spec.rb index 4eb799d..2577723 100644 --- a/spec/unit/puppet/parser/functions/fqdn_rotate_spec.rb +++ b/spec/unit/puppet/parser/functions/fqdn_rotate_spec.rb @@ -19,7 +19,7 @@ describe "the fqdn_rotate function" do end it "should rotate a string to give the same results for one host" do - scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1").twice + scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1").twice scope.function_fqdn_rotate(["abcdefg"]).should eql(scope.function_fqdn_rotate(["abcdefg"])) end diff --git a/spec/unit/puppet/parser/functions/get_module_path_spec.rb b/spec/unit/puppet/parser/functions/get_module_path_spec.rb index e761706..486bef6 100644 --- a/spec/unit/puppet/parser/functions/get_module_path_spec.rb +++ b/spec/unit/puppet/parser/functions/get_module_path_spec.rb @@ -15,11 +15,11 @@ describe Puppet::Parser::Functions.function(:get_module_path) do end it 'should only allow one argument' do - expect { scope.function_get_module_path([]) }.should raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) - expect { scope.function_get_module_path(['1','2','3']) }.should raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) + expect { scope.function_get_module_path([]) }.to raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) + expect { scope.function_get_module_path(['1','2','3']) }.to raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/) end it 'should raise an exception when the module cannot be found' do - expect { scope.function_get_module_path(['foo']) }.should raise_error(Puppet::ParseError, /Could not find module/) + expect { scope.function_get_module_path(['foo']) }.to raise_error(Puppet::ParseError, /Could not find module/) end describe 'when locating a module' do let(:modulepath) { "/tmp/does_not_exist" } diff --git a/spec/unit/puppet/parser/functions/intersection_spec.rb b/spec/unit/puppet/parser/functions/intersection_spec.rb new file mode 100644 index 0000000..fd44f7f --- /dev/null +++ b/spec/unit/puppet/parser/functions/intersection_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the intersection function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("intersection").should == "function_intersection" + end + + it "should raise a ParseError if there are fewer than 2 arguments" do + lambda { scope.function_intersection([]) }.should( raise_error(Puppet::ParseError) ) + end + + it "should return the intersection of two arrays" do + result = scope.function_intersection([["a","b","c"],["b","c","d"]]) + result.should(eq(["b","c"])) + end +end diff --git a/spec/unit/puppet/parser/functions/is_bool_spec.rb b/spec/unit/puppet/parser/functions/is_bool_spec.rb new file mode 100644 index 0000000..c94e83a --- /dev/null +++ b/spec/unit/puppet/parser/functions/is_bool_spec.rb @@ -0,0 +1,44 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the is_bool function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("is_bool").should == "function_is_bool" + end + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { scope.function_is_bool([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should return true if passed a TrueClass" do + result = scope.function_is_bool([true]) + result.should(eq(true)) + end + + it "should return true if passed a FalseClass" do + result = scope.function_is_bool([false]) + result.should(eq(true)) + end + + it "should return false if passed the string 'true'" do + result = scope.function_is_bool(['true']) + result.should(eq(false)) + end + + it "should return false if passed the string 'false'" do + result = scope.function_is_bool(['false']) + result.should(eq(false)) + end + + it "should return false if passed an array" do + result = scope.function_is_bool([["a","b"]]) + result.should(eq(false)) + end + + it "should return false if passed a hash" do + result = scope.function_is_bool([{"a" => "b"}]) + result.should(eq(false)) + end +end diff --git a/spec/unit/puppet/parser/functions/is_function_available.rb b/spec/unit/puppet/parser/functions/is_function_available.rb new file mode 100644 index 0000000..d5669a7 --- /dev/null +++ b/spec/unit/puppet/parser/functions/is_function_available.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the is_function_available function" do + before :all do + Puppet::Parser::Functions.autoloader.loadall + end + + before :each do + @scope = Puppet::Parser::Scope.new + end + + it "should exist" do + Puppet::Parser::Functions.function("is_function_available").should == "function_is_function_available" + end + + it "should raise a ParseError if there is less than 1 arguments" do + lambda { @scope.function_is_function_available([]) }.should( raise_error(Puppet::ParseError)) + end + + it "should return false if a nonexistent function is passed" do + result = @scope.function_is_function_available(['jeff_mccunes_left_sock']) + result.should(eq(false)) + end + + it "should return true if an available function is passed" do + result = @scope.function_is_function_available(['require']) + result.should(eq(true)) + end + +end diff --git a/spec/unit/puppet/parser/functions/is_integer_spec.rb b/spec/unit/puppet/parser/functions/is_integer_spec.rb index 4335795..24141cc 100644 --- a/spec/unit/puppet/parser/functions/is_integer_spec.rb +++ b/spec/unit/puppet/parser/functions/is_integer_spec.rb @@ -17,6 +17,11 @@ describe "the is_integer function" do result.should(eq(true)) end + it "should return true if a negative integer" do + result = scope.function_is_integer(["-7"]) + result.should(eq(true)) + end + it "should return false if a float" do result = scope.function_is_integer(["3.2"]) result.should(eq(false)) @@ -31,4 +36,34 @@ describe "the is_integer function" do result = scope.function_is_integer([3*2]) result.should(eq(true)) end + + it "should return false if an array" do + result = scope.function_is_numeric([["asdf"]]) + result.should(eq(false)) + end + + it "should return false if a hash" do + result = scope.function_is_numeric([{"asdf" => false}]) + result.should(eq(false)) + end + + it "should return false if a boolean" do + result = scope.function_is_numeric([true]) + result.should(eq(false)) + end + + it "should return false if a whitespace is in the string" do + result = scope.function_is_numeric([" -1324"]) + result.should(eq(false)) + end + + it "should return false if it is zero prefixed" do + result = scope.function_is_numeric(["0001234"]) + result.should(eq(false)) + end + + it "should return false if it is wrapped inside an array" do + result = scope.function_is_numeric([[1234]]) + result.should(eq(false)) + end end diff --git a/spec/unit/puppet/parser/functions/is_numeric_spec.rb b/spec/unit/puppet/parser/functions/is_numeric_spec.rb index d7440fb..1df1497 100644 --- a/spec/unit/puppet/parser/functions/is_numeric_spec.rb +++ b/spec/unit/puppet/parser/functions/is_numeric_spec.rb @@ -36,4 +36,84 @@ describe "the is_numeric function" do result = scope.function_is_numeric(["asdf"]) result.should(eq(false)) end + + it "should return false if an array" do + result = scope.function_is_numeric([["asdf"]]) + result.should(eq(false)) + end + + it "should return false if an array of integers" do + result = scope.function_is_numeric([[1,2,3,4]]) + result.should(eq(false)) + end + + it "should return false if a hash" do + result = scope.function_is_numeric([{"asdf" => false}]) + result.should(eq(false)) + end + + it "should return false if a hash with numbers in it" do + result = scope.function_is_numeric([{1 => 2}]) + result.should(eq(false)) + end + + it "should return false if a boolean" do + result = scope.function_is_numeric([true]) + result.should(eq(false)) + end + + it "should return true if a negative float with exponent" do + result = scope.function_is_numeric(["-342.2315e-12"]) + result.should(eq(true)) + end + + it "should return false if a negative integer with whitespaces before/after the dash" do + result = scope.function_is_numeric([" - 751"]) + result.should(eq(false)) + end + +# it "should return true if a hexadecimal" do +# result = scope.function_is_numeric(["0x52F8c"]) +# result.should(eq(true)) +# end +# +# it "should return true if a hexadecimal with uppercase 0X prefix" do +# result = scope.function_is_numeric(["0X52F8c"]) +# result.should(eq(true)) +# end +# +# it "should return false if a hexadecimal without a prefix" do +# result = scope.function_is_numeric(["52F8c"]) +# result.should(eq(false)) +# end +# +# it "should return true if a octal" do +# result = scope.function_is_numeric(["0751"]) +# result.should(eq(true)) +# end +# +# it "should return true if a negative hexadecimal" do +# result = scope.function_is_numeric(["-0x52F8c"]) +# result.should(eq(true)) +# end +# +# it "should return true if a negative octal" do +# result = scope.function_is_numeric(["-0751"]) +# result.should(eq(true)) +# end +# +# it "should return false if a negative octal with whitespaces before/after the dash" do +# result = scope.function_is_numeric([" - 0751"]) +# result.should(eq(false)) +# end +# +# it "should return false if a bad hexadecimal" do +# result = scope.function_is_numeric(["0x23d7g"]) +# result.should(eq(false)) +# end +# +# it "should return false if a bad octal" do +# result = scope.function_is_numeric(["0287"]) +# result.should(eq(false)) +# end end diff --git a/spec/unit/puppet/parser/functions/loadyaml_spec.rb b/spec/unit/puppet/parser/functions/loadyaml_spec.rb new file mode 100644 index 0000000..fe16318 --- /dev/null +++ b/spec/unit/puppet/parser/functions/loadyaml_spec.rb @@ -0,0 +1,25 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the loadyaml function" do + include PuppetlabsSpec::Files + + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("loadyaml").should == "function_loadyaml" + end + + it "should raise a ParseError if there is less than 1 arguments" do + expect { scope.function_loadyaml([]) }.to raise_error(Puppet::ParseError) + end + + it "should convert YAML file to a data structure" do + yaml_file = tmpfilename ('yamlfile') + File.open(yaml_file, 'w') do |fh| + fh.write("---\n aaa: 1\n bbb: 2\n ccc: 3\n ddd: 4\n") + end + result = scope.function_loadyaml([yaml_file]) + result.should == {"aaa" => 1, "bbb" => 2, "ccc" => 3, "ddd" => 4 } + end +end diff --git a/spec/unit/puppet/parser/functions/merge_spec.rb b/spec/unit/puppet/parser/functions/merge_spec.rb index 79079b6..15a5d94 100644 --- a/spec/unit/puppet/parser/functions/merge_spec.rb +++ b/spec/unit/puppet/parser/functions/merge_spec.rb @@ -30,7 +30,7 @@ describe Puppet::Parser::Functions.function(:merge) do end it 'should accept empty strings as puppet undef' do - expect { new_hash = scope.function_merge([{}, ''])}.to raise_error + expect { new_hash = scope.function_merge([{}, ''])}.not_to raise_error end it 'should be able to merge two hashes' do diff --git a/spec/unit/puppet/parser/functions/num2bool_spec.rb b/spec/unit/puppet/parser/functions/num2bool_spec.rb index 640c689..b56196d 100644 --- a/spec/unit/puppet/parser/functions/num2bool_spec.rb +++ b/spec/unit/puppet/parser/functions/num2bool_spec.rb @@ -8,17 +8,60 @@ describe "the num2bool function" do Puppet::Parser::Functions.function("num2bool").should == "function_num2bool" end - it "should raise a ParseError if there is less than 1 arguments" do + it "should raise a ParseError if there are no arguments" do lambda { scope.function_num2bool([]) }.should( raise_error(Puppet::ParseError)) end - it "should return true if 1" do + it "should raise a ParseError if there are more than 1 arguments" do + lambda { scope.function_num2bool(["foo","bar"]) }.should( raise_error(Puppet::ParseError)) + end + + it "should raise a ParseError if passed something non-numeric" do + lambda { scope.function_num2bool(["xyzzy"]) }.should( raise_error(Puppet::ParseError)) + end + + it "should return true if passed string 1" do result = scope.function_num2bool(["1"]) result.should(be_true) end - it "should return false if 0" do + it "should return true if passed string 1.5" do + result = scope.function_num2bool(["1.5"]) + result.should(be_true) + end + + it "should return true if passed number 1" do + result = scope.function_num2bool([1]) + result.should(be_true) + end + + it "should return false if passed string 0" do result = scope.function_num2bool(["0"]) result.should(be_false) end + + it "should return false if passed number 0" do + result = scope.function_num2bool([0]) + result.should(be_false) + end + + it "should return false if passed string -1" do + result = scope.function_num2bool(["-1"]) + result.should(be_false) + end + + it "should return false if passed string -1.5" do + result = scope.function_num2bool(["-1.5"]) + result.should(be_false) + end + + it "should return false if passed number -1" do + result = scope.function_num2bool([-1]) + result.should(be_false) + end + + it "should return false if passed float -1.5" do + result = scope.function_num2bool([-1.5]) + result.should(be_false) + end end diff --git a/spec/unit/puppet/parser/functions/pick_default_spec.rb b/spec/unit/puppet/parser/functions/pick_default_spec.rb new file mode 100644 index 0000000..c9235b5 --- /dev/null +++ b/spec/unit/puppet/parser/functions/pick_default_spec.rb @@ -0,0 +1,58 @@ +#!/usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the pick_default function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("pick_default").should == "function_pick_default" + end + + it 'should return the correct value' do + scope.function_pick_default(['first', 'second']).should == 'first' + end + + it 'should return the correct value if the first value is empty' do + scope.function_pick_default(['', 'second']).should == 'second' + end + + it 'should skip empty string values' do + scope.function_pick_default(['', 'first']).should == 'first' + end + + it 'should skip :undef values' do + scope.function_pick_default([:undef, 'first']).should == 'first' + end + + it 'should skip :undefined values' do + scope.function_pick_default([:undefined, 'first']).should == 'first' + end + + it 'should return the empty string if it is the last possibility' do + scope.function_pick_default([:undef, :undefined, '']).should == '' + end + + it 'should return :undef if it is the last possibility' do + scope.function_pick_default(['', :undefined, :undef]).should == :undef + end + + it 'should return :undefined if it is the last possibility' do + scope.function_pick_default([:undef, '', :undefined]).should == :undefined + end + + it 'should return the empty string if it is the only possibility' do + scope.function_pick_default(['']).should == '' + end + + it 'should return :undef if it is the only possibility' do + scope.function_pick_default([:undef]).should == :undef + end + + it 'should return :undefined if it is the only possibility' do + scope.function_pick_default([:undefined]).should == :undefined + end + + it 'should error if no values are passed' do + expect { scope.function_pick_default([]) }.to raise_error(Puppet::Error, /Must receive at least one argument./) + end +end diff --git a/spec/unit/puppet/parser/functions/pick_spec.rb b/spec/unit/puppet/parser/functions/pick_spec.rb index 761db6b..f53fa80 100644..100755 --- a/spec/unit/puppet/parser/functions/pick_spec.rb +++ b/spec/unit/puppet/parser/functions/pick_spec.rb @@ -29,6 +29,6 @@ describe "the pick function" do end it 'should error if no values are passed' do - expect { scope.function_pick([]) }.to raise_error(Puppet::Error, /Must provide non empty value./) + expect { scope.function_pick([]) }.to( raise_error(Puppet::ParseError, "pick(): must receive at least one non empty value")) end end diff --git a/spec/unit/puppet/parser/functions/prefix_spec.rb b/spec/unit/puppet/parser/functions/prefix_spec.rb index 5cf592b..6e8ddc5 100644 --- a/spec/unit/puppet/parser/functions/prefix_spec.rb +++ b/spec/unit/puppet/parser/functions/prefix_spec.rb @@ -4,15 +4,24 @@ require 'spec_helper' describe "the prefix function" do let(:scope) { PuppetlabsSpec::PuppetInternals.scope } - it "should exist" do - Puppet::Parser::Functions.function("prefix").should == "function_prefix" + it "raises a ParseError if there is less than 1 arguments" do + expect { scope.function_prefix([]) }.to raise_error(Puppet::ParseError, /number of arguments/) end - it "should raise a ParseError if there is less than 1 arguments" do - lambda { scope.function_prefix([]) }.should( raise_error(Puppet::ParseError)) + it "raises an error if the first argument is not an array" do + expect { + scope.function_prefix([Object.new]) + }.to raise_error(Puppet::ParseError, /expected first argument to be an Array/) end - it "should return a prefixed array" do + + it "raises an error if the second argument is not a string" do + expect { + scope.function_prefix([['first', 'second'], 42]) + }.to raise_error(Puppet::ParseError, /expected second argument to be a String/) + end + + it "returns a prefixed array" do result = scope.function_prefix([['a','b','c'], 'p']) result.should(eq(['pa','pb','pc'])) end diff --git a/spec/unit/puppet/parser/functions/range_spec.rb b/spec/unit/puppet/parser/functions/range_spec.rb index 42751f4..0e1ad37 100644 --- a/spec/unit/puppet/parser/functions/range_spec.rb +++ b/spec/unit/puppet/parser/functions/range_spec.rb @@ -4,31 +4,67 @@ require 'spec_helper' describe "the range function" do let(:scope) { PuppetlabsSpec::PuppetInternals.scope } - it "should exist" do + it "exists" do Puppet::Parser::Functions.function("range").should == "function_range" end - it "should raise a ParseError if there is less than 1 arguments" do - lambda { scope.function_range([]) }.should( raise_error(Puppet::ParseError)) + it "raises a ParseError if there is less than 1 arguments" do + expect { scope.function_range([]) }.to raise_error Puppet::ParseError, /Wrong number of arguments.*0 for 1/ end - it "should return a letter range" do - result = scope.function_range(["a","d"]) - result.should(eq(['a','b','c','d'])) - end + describe 'with a letter range' do + it "returns a letter range" do + result = scope.function_range(["a","d"]) + result.should eq ['a','b','c','d'] + end + + it "returns a letter range given a step of 1" do + result = scope.function_range(["a","d","1"]) + result.should eq ['a','b','c','d'] + end - it "should return a number range" do - result = scope.function_range(["1","4"]) - result.should(eq([1,2,3,4])) + it "returns a stepped letter range" do + result = scope.function_range(["a","d","2"]) + result.should eq ['a','c'] + end + + it "returns a stepped letter range given a negative step" do + result = scope.function_range(["a","d","-2"]) + result.should eq ['a','c'] + end end - it "should work with padded hostname like strings" do - expected = ("host01".."host10").to_a - scope.function_range(["host01","host10"]).should eq expected + describe 'with a number range' do + it "returns a number range" do + result = scope.function_range(["1","4"]) + result.should eq [1,2,3,4] + end + + it "returns a number range given a step of 1" do + result = scope.function_range(["1","4","1"]) + result.should eq [1,2,3,4] + end + + it "returns a stepped number range" do + result = scope.function_range(["1","4","2"]) + result.should eq [1,3] + end + + it "returns a stepped number range given a negative step" do + result = scope.function_range(["1","4","-2"]) + result.should eq [1,3] + end end - it "should coerce zero padded digits to integers" do - expected = (0..10).to_a - scope.function_range(["00", "10"]).should eq expected + describe 'with a numeric-like string range' do + it "works with padded hostname like strings" do + expected = ("host01".."host10").to_a + scope.function_range(["host01","host10"]).should eq expected + end + + it "coerces zero padded digits to integers" do + expected = (0..10).to_a + scope.function_range(["00", "10"]).should eq expected + end end end diff --git a/spec/unit/puppet/parser/functions/str2bool_spec.rb b/spec/unit/puppet/parser/functions/str2bool_spec.rb index ef6350f..73c09c7 100644 --- a/spec/unit/puppet/parser/functions/str2bool_spec.rb +++ b/spec/unit/puppet/parser/functions/str2bool_spec.rb @@ -21,7 +21,7 @@ describe "the str2bool function" do result = scope.function_str2bool(["undef"]) result.should(eq(false)) end - + it "should return the boolean it was called with" do result = scope.function_str2bool([true]) result.should(eq(true)) diff --git a/spec/unit/puppet/parser/functions/str2saltedsha512_spec.rb b/spec/unit/puppet/parser/functions/str2saltedsha512_spec.rb index a692c31..df8fb8e 100644 --- a/spec/unit/puppet/parser/functions/str2saltedsha512_spec.rb +++ b/spec/unit/puppet/parser/functions/str2saltedsha512_spec.rb @@ -9,11 +9,11 @@ describe "the str2saltedsha512 function" do end it "should raise a ParseError if there is less than 1 argument" do - expect { scope.function_str2saltedsha512([]) }.should( raise_error(Puppet::ParseError) ) + expect { scope.function_str2saltedsha512([]) }.to( raise_error(Puppet::ParseError) ) end it "should raise a ParseError if there is more than 1 argument" do - expect { scope.function_str2saltedsha512(['foo', 'bar', 'baz']) }.should( raise_error(Puppet::ParseError) ) + expect { scope.function_str2saltedsha512(['foo', 'bar', 'baz']) }.to( raise_error(Puppet::ParseError) ) end it "should return a salted-sha512 password hash 136 characters in length" do @@ -22,7 +22,7 @@ describe "the str2saltedsha512 function" do end it "should raise an error if you pass a non-string password" do - expect { scope.function_str2saltedsha512([1234]) }.should( raise_error(Puppet::ParseError) ) + expect { scope.function_str2saltedsha512([1234]) }.to( raise_error(Puppet::ParseError) ) end it "should generate a valid password" do diff --git a/spec/unit/puppet/parser/functions/suffix_spec.rb b/spec/unit/puppet/parser/functions/suffix_spec.rb new file mode 100644 index 0000000..89ba3b8 --- /dev/null +++ b/spec/unit/puppet/parser/functions/suffix_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the suffix function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "raises a ParseError if there is less than 1 arguments" do + expect { scope.function_suffix([]) }.to raise_error(Puppet::ParseError, /number of arguments/) + end + + it "raises an error if the first argument is not an array" do + expect { + scope.function_suffix([Object.new]) + }.to raise_error(Puppet::ParseError, /expected first argument to be an Array/) + end + + it "raises an error if the second argument is not a string" do + expect { + scope.function_suffix([['first', 'second'], 42]) + }.to raise_error(Puppet::ParseError, /expected second argument to be a String/) + end + + it "returns a suffixed array" do + result = scope.function_suffix([['a','b','c'], 'p']) + result.should(eq(['ap','bp','cp'])) + end +end diff --git a/spec/unit/puppet/parser/functions/union_spec.rb b/spec/unit/puppet/parser/functions/union_spec.rb new file mode 100644 index 0000000..0d282ca --- /dev/null +++ b/spec/unit/puppet/parser/functions/union_spec.rb @@ -0,0 +1,19 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the union function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + Puppet::Parser::Functions.function("union").should == "function_union" + end + + it "should raise a ParseError if there are fewer than 2 arguments" do + lambda { scope.function_union([]) }.should( raise_error(Puppet::ParseError) ) + end + + it "should join two arrays together" do + result = scope.function_union([["a","b","c"],["b","c","d"]]) + result.should(eq(["a","b","c","d"])) + end +end diff --git a/spec/unit/puppet/parser/functions/uriescape_spec.rb b/spec/unit/puppet/parser/functions/uriescape_spec.rb index 371de46..7211c88 100644 --- a/spec/unit/puppet/parser/functions/uriescape_spec.rb +++ b/spec/unit/puppet/parser/functions/uriescape_spec.rb @@ -13,8 +13,8 @@ describe "the uriescape function" do end it "should uriescape a string" do - result = scope.function_uriescape([":/?#[]@!$&'()*+,;= "]) - result.should(eq('%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D%20')) + result = scope.function_uriescape([":/?#[]@!$&'()*+,;= \"{}"]) + result.should(eq(':/?%23[]@!$&\'()*+,;=%20%22%7B%7D')) end it "should do nothing if a string is already safe" do diff --git a/spec/unit/puppet/parser/functions/validate_array_spec.rb b/spec/unit/puppet/parser/functions/validate_array_spec.rb index 8eee72a..4b31cfd 100644 --- a/spec/unit/puppet/parser/functions/validate_array_spec.rb +++ b/spec/unit/puppet/parser/functions/validate_array_spec.rb @@ -9,12 +9,12 @@ describe Puppet::Parser::Functions.function(:validate_array) do %w{ true false }.each do |the_string| it "should not compile when #{the_string} is a string" do Puppet[:code] = "validate_array('#{the_string}')" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not an Array/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/) end it "should not compile when #{the_string} is a bare word" do Puppet[:code] = "validate_array(#{the_string})" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not an Array/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/) end end @@ -32,7 +32,7 @@ describe Puppet::Parser::Functions.function(:validate_array) do $foo = undef validate_array($foo) ENDofPUPPETcode - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not an Array/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/) end end end diff --git a/spec/unit/puppet/parser/functions/validate_augeas_spec.rb b/spec/unit/puppet/parser/functions/validate_augeas_spec.rb new file mode 100644 index 0000000..ab5c140 --- /dev/null +++ b/spec/unit/puppet/parser/functions/validate_augeas_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +describe Puppet::Parser::Functions.function(:validate_augeas), :if => Puppet.features.augeas? do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + # The subject of these examplres is the method itself. + subject do + # This makes sure the function is loaded within each test + function_name = Puppet::Parser::Functions.function(:validate_augeas) + scope.method(function_name) + end + + context 'Using Puppet::Parser::Scope.new' do + + describe 'Garbage inputs' do + inputs = [ + [ nil ], + [ [ nil ] ], + [ { 'foo' => 'bar' } ], + [ { } ], + [ '' ], + [ "one", "one", "MSG to User", "4th arg" ], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should fail" do + expect { subject.call [input] }.to raise_error Puppet::ParseError + end + end + end + + describe 'Valid inputs' do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns' ], + [ "proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns'], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should not fail" do + expect { subject.call input }.not_to raise_error + end + end + end + + describe "Valid inputs which should raise an exception without a message" do + # The intent here is to make sure valid inputs raise exceptions when they + # don't specify an error message to display. This is the behvior in + # 2.2.x and prior. + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns' ], + [ "127.0.1.1\n", 'Hosts.lns' ], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should fail" do + expect { subject.call input }.to raise_error /validate_augeas.*?matched less than it should/ + end + end + end + + describe "Nicer Error Messages" do + # The intent here is to make sure the function returns the 3rd argument + # in the exception thrown + inputs = [ + [ "root:x:0:0:root\n", 'Passwd.lns', [], 'Failed to validate passwd content' ], + [ "127.0.1.1\n", 'Hosts.lns', [], 'Wrong hosts content' ], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should fail" do + expect { subject.call input }.to raise_error /#{input[2]}/ + end + end + end + + describe "Passing simple unit tests" do + inputs = [ + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should fail" do + expect { subject.call input }.not_to raise_error + end + end + end + + describe "Failing simple unit tests" do + inputs = [ + [ "foobar:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']], + [ "root:x:0:0:root:/root:/bin/sh\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']], + ] + + inputs.each do |input| + it "validate_augeas(#{input.inspect}) should fail" do + expect { subject.call input }.to raise_error /testing path/ + end + end + end + end +end diff --git a/spec/unit/puppet/parser/functions/validate_bool_spec.rb b/spec/unit/puppet/parser/functions/validate_bool_spec.rb index 31ab8fb..261fb23 100644 --- a/spec/unit/puppet/parser/functions/validate_bool_spec.rb +++ b/spec/unit/puppet/parser/functions/validate_bool_spec.rb @@ -10,7 +10,7 @@ describe Puppet::Parser::Functions.function(:validate_bool) do it "should not compile when #{the_string} is a string" do Puppet[:code] = "validate_bool('#{the_string}')" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a boolean/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/) end it "should compile when #{the_string} is a bare word" do @@ -22,12 +22,12 @@ describe Puppet::Parser::Functions.function(:validate_bool) do it "should not compile when an arbitrary string is passed" do Puppet[:code] = 'validate_bool("jeff and dan are awesome")' - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a boolean/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/) end it "should not compile when no arguments are passed" do Puppet[:code] = 'validate_bool()' - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /wrong number of arguments/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /wrong number of arguments/) end it "should compile when multiple boolean arguments are passed" do @@ -45,7 +45,7 @@ describe Puppet::Parser::Functions.function(:validate_bool) do $bar = false validate_bool($foo, $bar, true, false, 'jeff') ENDofPUPPETcode - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a boolean/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/) end end end diff --git a/spec/unit/puppet/parser/functions/validate_cmd_spec.rb b/spec/unit/puppet/parser/functions/validate_cmd_spec.rb new file mode 100644 index 0000000..a86cb01 --- /dev/null +++ b/spec/unit/puppet/parser/functions/validate_cmd_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +TESTEXE = File.exists?('/usr/bin/test') ? '/usr/bin/test' : '/bin/test' +TOUCHEXE = File.exists?('/usr/bin/touch') ? '/usr/bin/touch' : '/bin/touch' + +describe Puppet::Parser::Functions.function(:validate_cmd) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + subject do + function_name = Puppet::Parser::Functions.function(:validate_cmd) + scope.method(function_name) + end + + describe "with an explicit failure message" do + it "prints the failure message on error" do + expect { + subject.call ['', '/bin/false', 'failure message!'] + }.to raise_error Puppet::ParseError, /failure message!/ + end + end + + describe "on validation failure" do + it "includes the command error output" do + expect { + subject.call ['', "#{TOUCHEXE} /cant/touch/this"] + }.to raise_error Puppet::ParseError, /(cannot touch|o such file or)/ + end + + it "includes the command return value" do + expect { + subject.call ['', '/cant/run/this'] + }.to raise_error Puppet::ParseError, /returned 1\b/ + end + end + + describe "when performing actual validation" do + it "can positively validate file content" do + expect { subject.call ["non-empty", "#{TESTEXE} -s"] }.to_not raise_error + end + + it "can negatively validate file content" do + expect { + subject.call ["", "#{TESTEXE} -s"] + }.to raise_error Puppet::ParseError, /failed to validate.*test -s/ + end + end +end diff --git a/spec/unit/puppet/parser/functions/validate_hash_spec.rb b/spec/unit/puppet/parser/functions/validate_hash_spec.rb index f63db1d..a0c35c2 100644 --- a/spec/unit/puppet/parser/functions/validate_hash_spec.rb +++ b/spec/unit/puppet/parser/functions/validate_hash_spec.rb @@ -11,12 +11,12 @@ describe Puppet::Parser::Functions.function(:validate_hash) do it "should not compile when #{the_string} is a string" do Puppet[:code] = "validate_hash('#{the_string}')" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a Hash/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/) end it "should not compile when #{the_string} is a bare word" do Puppet[:code] = "validate_hash(#{the_string})" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a Hash/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/) end end @@ -35,10 +35,9 @@ describe Puppet::Parser::Functions.function(:validate_hash) do $foo = undef validate_hash($foo) ENDofPUPPETcode - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a Hash/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/) end end end - diff --git a/spec/unit/puppet/parser/functions/validate_ipv4_address_spec.rb b/spec/unit/puppet/parser/functions/validate_ipv4_address_spec.rb new file mode 100644 index 0000000..85536d3 --- /dev/null +++ b/spec/unit/puppet/parser/functions/validate_ipv4_address_spec.rb @@ -0,0 +1,64 @@ +#! /usr/bin/env/ruby -S rspec + +require "spec_helper" + +describe Puppet::Parser::Functions.function(:validate_ipv4_address) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe "when calling validate_ipv4_address from puppet" do + describe "when given IPv4 address strings" do + it "should compile with one argument" do + Puppet[:code] = "validate_ipv4_address('1.2.3.4')" + scope.compiler.compile + end + + it "should compile with multiple arguments" do + Puppet[:code] = "validate_ipv4_address('1.2.3.4', '5.6.7.8')" + scope.compiler.compile + end + end + + describe "when given an IPv6 address" do + it "should not compile" do + Puppet[:code] = "validate_ipv4_address('3ffe:505')" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /not a valid IPv4 address/) + end + end + + describe "when given other strings" do + it "should not compile" do + Puppet[:code] = "validate_ipv4_address('hello', 'world')" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /not a valid IPv4 address/) + end + end + + describe "when given numbers" do + it "should not compile" do + Puppet[:code] = "validate_ipv4_address(1, 2)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /is not a valid IPv4 address/) + end + end + + describe "when given booleans" do + it "should not compile" do + Puppet[:code] = "validate_ipv4_address(true, false)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /is not a string/) + end + end + + it "should not compile when no arguments are passed" do + Puppet[:code] = "validate_ipv4_address()" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end +end diff --git a/spec/unit/puppet/parser/functions/validate_ipv6_address_spec.rb b/spec/unit/puppet/parser/functions/validate_ipv6_address_spec.rb new file mode 100644 index 0000000..1fe5304 --- /dev/null +++ b/spec/unit/puppet/parser/functions/validate_ipv6_address_spec.rb @@ -0,0 +1,67 @@ +#! /usr/bin/env/ruby -S rspec + +require "spec_helper" + +describe Puppet::Parser::Functions.function(:validate_ipv6_address) do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + describe "when calling validate_ipv6_address from puppet" do + describe "when given IPv6 address strings" do + it "should compile with one argument" do + Puppet[:code] = "validate_ipv6_address('3ffe:0505:0002::')" + scope.compiler.compile + end + + it "should compile with multiple arguments" do + Puppet[:code] = "validate_ipv6_address('3ffe:0505:0002::', '3ffe:0505:0001::')" + scope.compiler.compile + end + end + + describe "when given an ipv4 address" do + it "should not compile" do + Puppet[:code] = "validate_ipv6_address('1.2.3.4')" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/) + end + end + + describe "when given other strings" do + it "should not compile" do + Puppet[:code] = "validate_ipv6_address('hello', 'world')" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/) + end + end + + # 1.8.7 is EOL'd and also absolutely insane about ipv6 + unless RUBY_VERSION == '1.8.7' + describe "when given numbers" do + it "should not compile" do + Puppet[:code] = "validate_ipv6_address(1, 2)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/) + end + end + end + + describe "when given booleans" do + it "should not compile" do + Puppet[:code] = "validate_ipv6_address(true, false)" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /is not a string/) + end + end + + it "should not compile when no arguments are passed" do + Puppet[:code] = "validate_ipv6_address()" + expect { + scope.compiler.compile + }.to raise_error(Puppet::ParseError, /wrong number of arguments/) + end + end +end diff --git a/spec/unit/puppet/parser/functions/validate_slength_spec.rb b/spec/unit/puppet/parser/functions/validate_slength_spec.rb index eccf908..851835f 100755 --- a/spec/unit/puppet/parser/functions/validate_slength_spec.rb +++ b/spec/unit/puppet/parser/functions/validate_slength_spec.rb @@ -9,40 +9,59 @@ describe "the validate_slength function" do Puppet::Parser::Functions.function("validate_slength").should == "function_validate_slength" end - it "should raise a ParseError if there is less than 2 arguments" do - expect { scope.function_validate_slength([]) }.to(raise_error(Puppet::ParseError)) - expect { scope.function_validate_slength(["asdf"]) }.to(raise_error(Puppet::ParseError)) - end + describe "validating the input argument types" do + it "raises an error if there are less than two arguments" do + expect { scope.function_validate_slength([]) }.to raise_error Puppet::ParseError, /Wrong number of arguments/ + end - it "should raise a ParseError if argument 2 doesn't convert to a fixnum" do - expect { scope.function_validate_slength(["moo",["2"]]) }.to(raise_error(Puppet::ParseError, /Couldn't convert whatever you passed/)) - end + it "raises an error if there are more than three arguments" do + expect { scope.function_validate_slength(['input', 1, 2, 3]) }.to raise_error Puppet::ParseError, /Wrong number of arguments/ + end - it "should raise a ParseError if argument 2 converted, but to 0, e.g. a string" do - expect { scope.function_validate_slength(["moo","monkey"]) }.to(raise_error(Puppet::ParseError, /please pass a positive number as max_length/)) - end + it "raises an error if the first argument is not a string" do + expect { scope.function_validate_slength([Object.new, 2, 1]) }.to raise_error Puppet::ParseError, /Expected first argument.*got .*Object/ + end - it "should raise a ParseError if argument 2 converted, but to 0" do - expect { scope.function_validate_slength(["moo","0"]) }.to(raise_error(Puppet::ParseError, /please pass a positive number as max_length/)) - end + it "raises an error if the second argument cannot be cast to an Integer" do + expect { scope.function_validate_slength(['input', Object.new]) }.to raise_error Puppet::ParseError, /Expected second argument.*got .*Object/ + end - it "should fail if string greater then size" do - expect { scope.function_validate_slength(["test", 2]) }.to(raise_error(Puppet::ParseError, /It should have been less than or equal to/)) - end + it "raises an error if the third argument cannot be cast to an Integer" do + expect { scope.function_validate_slength(['input', 1, Object.new]) }.to raise_error Puppet::ParseError, /Expected third argument.*got .*Object/ + end - it "should fail if you pass an array of something other than strings" do - expect { scope.function_validate_slength([["moo",["moo"],Hash.new["moo" => 7]], 7]) }.to(raise_error(Puppet::ParseError, /is not a string, it's a/)) + it "raises an error if the second argument is smaller than the third argument" do + expect { scope.function_validate_slength(['input', 1, 2]) }.to raise_error Puppet::ParseError, /Expected second argument to be larger than third argument/ + end end - it "should fail if you pass something other than a string or array" do - expect { scope.function_validate_slength([Hash.new["moo" => "7"],6]) }.to(raise_error(Puppet::ParseError), /please pass a string, or an array of strings/) - end + describe "validating the input string length" do + describe "when the input is a string" do + it "fails validation if the string is larger than the max length" do + expect { scope.function_validate_slength(['input', 1]) }.to raise_error Puppet::ParseError, /Expected length .* between 0 and 1, was 5/ + end - it "should not fail if string is smaller or equal to size" do - expect { scope.function_validate_slength(["test", 5]) }.to_not(raise_error(Puppet::ParseError)) - end + it "fails validation if the string is less than the min length" do + expect { scope.function_validate_slength(['input', 10, 6]) }.to raise_error Puppet::ParseError, /Expected length .* between 6 and 10, was 5/ + end + + it "doesn't raise an error if the string is under the max length" do + scope.function_validate_slength(['input', 10]) + end + + it "doesn't raise an error if the string is equal to the max length" do + scope.function_validate_slength(['input', 5]) + end + + it "doesn't raise an error if the string is equal to the min length" do + scope.function_validate_slength(['input', 10, 5]) + end + end - it "should not fail if array of string is are all smaller or equal to size" do - expect { scope.function_validate_slength([["moo","foo","bar"], 5]) }.to_not(raise_error(Puppet::ParseError)) + describe "when the input is an array" do + it "fails validation if one of the array elements is not a string" do + expect { scope.function_validate_slength([["a", "b", Object.new], 2]) }.to raise_error Puppet::ParseError, /Expected element at array position 2 .*String, got .*Object/ + end + end end end diff --git a/spec/unit/puppet/parser/functions/validate_string_spec.rb b/spec/unit/puppet/parser/functions/validate_string_spec.rb index f40bf2a..3b4fb3e 100644 --- a/spec/unit/puppet/parser/functions/validate_string_spec.rb +++ b/spec/unit/puppet/parser/functions/validate_string_spec.rb @@ -29,7 +29,7 @@ describe Puppet::Parser::Functions.function(:validate_string) do it "should not compile when #{the_string} is a bare word" do Puppet[:code] = "validate_string(#{the_string})" - expect { scope.compiler.compile }.should raise_error(Puppet::ParseError, /is not a string/) + expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a string/) end end @@ -58,4 +58,3 @@ describe Puppet::Parser::Functions.function(:validate_string) do end end end - diff --git a/spec/unit/puppet/provider/file_line/ruby_spec.rb b/spec/unit/puppet/provider/file_line/ruby_spec.rb index 50f5840..c356bd2 100644 --- a/spec/unit/puppet/provider/file_line/ruby_spec.rb +++ b/spec/unit/puppet/provider/file_line/ruby_spec.rb @@ -3,33 +3,36 @@ require 'tempfile' provider_class = Puppet::Type.type(:file_line).provider(:ruby) describe provider_class do context "when adding" 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 + let :tmpfile do tmp = Tempfile.new('tmp') - @tmpfile = tmp.path + path = tmp.path tmp.close! - @resource = Puppet::Type::File_line.new( - {:name => 'foo', :path => @tmpfile, :line => 'foo'} + path + end + let :resource do + Puppet::Type::File_line.new( + {:name => 'foo', :path => tmpfile, :line => 'foo'} ) - @provider = provider_class.new(@resource) 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| + File.open(tmpfile, 'w') do |fh| fh.write('foo') end - @provider.exists?.should be_true + provider.exists?.should be_true end it 'should detect if the line does not exist in the file' do - File.open(@tmpfile, 'w') do |fh| + File.open(tmpfile, 'w') do |fh| fh.write('foo1') end - @provider.exists?.should be_nil + provider.exists?.should be_nil end it 'should append to an existing file when creating' do - @provider.create - File.read(@tmpfile).chomp.should == 'foo' + provider.create + File.read(tmpfile).chomp.should == 'foo' end end @@ -52,38 +55,132 @@ describe provider_class do @provider = provider_class.new(@resource) end - 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") + 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 + @provider.exists?.should be_nil + expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) + File.read(@tmpfile).should eql("foo1\nfoo=blah\nfoo2\nfoo=baz") end - @provider.exists?.should be_nil - expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/) - File.read(@tmpfile).should eql("foo1\nfoo=blah\nfoo2\nfoo=baz") - end - it 'should replace a line that matches' do - File.open(@tmpfile, 'w') do |fh| - fh.write("foo1\nfoo=blah\nfoo2") + 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 + @provider.exists?.should be_nil + @provider.create + File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2\nfoo = bar") end - @provider.exists?.should be_nil - @provider.create - File.read(@tmpfile).chomp.should 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") + + 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 + @provider.exists?.should be_nil + @provider.create + File.read(@tmpfile).chomp.should 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 + @provider.exists?.should be_nil + @provider.create + File.read(@tmpfile).should 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 + @provider.exists?.should be_true + @provider.create + File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2") end - @provider.exists?.should be_nil - @provider.create - File.read(@tmpfile).should 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") + + describe 'using after' do + let :resource do + Puppet::Type::File_line.new( + { + :name => 'foo', + :path => @tmpfile, + :line => 'inserted = line', + :after => '^foo1', + } + ) + end + + let :provider do + provider_class.new(resource) + end + + context 'with one line matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz") + end + end + + it 'inserts the specified line after the line matching the "after" expression' do + provider.create + File.read(@tmpfile).chomp.should eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz") + end + end + + context 'with two lines matching the after expression' do + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz") + end + end + + it 'errors out stating "One or no line must match the pattern"' do + expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/) + end + end + + context 'with no lines matching the after expression' do + let :content do + "foo3\nfoo = blah\nfoo2\nfoo = baz\n" + end + + before :each do + File.open(@tmpfile, 'w') do |fh| + fh.write(content) + end + end + + it 'appends the specified line to the file' do + provider.create + File.read(@tmpfile).should eq(content << resource[:line] << "\n") + end end - @provider.exists?.should be_true - @provider.create - File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2") end end diff --git a/spec/unit/puppet/type/file_line_spec.rb b/spec/unit/puppet/type/file_line_spec.rb index 37f3861..34d5dad 100644 --- a/spec/unit/puppet/type/file_line_spec.rb +++ b/spec/unit/puppet/type/file_line_spec.rb @@ -37,13 +37,13 @@ describe Puppet::Type.type(:file_line) do file_line[:path].should == '/tmp/path' end it 'should not accept unqualified path' do - expect { file_line[:path] = 'file' }.should raise_error(Puppet::Error, /File paths must be fully qualified/) + expect { file_line[:path] = 'file' }.to raise_error(Puppet::Error, /File paths must be fully qualified/) end it 'should require that a line is specified' do - expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => '/tmp/file') }.should raise_error(Puppet::Error, /Both line and path are required attributes/) + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => '/tmp/file') }.to raise_error(Puppet::Error, /Both line and path are required attributes/) end it 'should require that a file is specified' do - expect { Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'path') }.should raise_error(Puppet::Error, /Both line and path are required attributes/) + expect { Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'path') }.to raise_error(Puppet::Error, /Both line and path are required attributes/) end it 'should default to ensure => present' do file_line[:ensure].should eq :present diff --git a/tests/has_interface_with.pp b/tests/has_interface_with.pp index 8b9e51e..e1f1353 100644 --- a/tests/has_interface_with.pp +++ b/tests/has_interface_with.pp @@ -1,10 +1,10 @@ include stdlib -info("has_interface_with('lo'):", has_interface_with('lo')) -info("has_interface_with('loX'):", has_interface_with('loX')) -info("has_interface_with('ipaddress', '127.0.0.1'):", has_interface_with('ipaddress', '127.0.0.1')) -info("has_interface_with('ipaddress', '127.0.0.100'):", has_interface_with('ipaddress', '127.0.0.100')) -info("has_interface_with('network', '127.0.0.0'):", has_interface_with('network', '127.0.0.0')) -info("has_interface_with('network', '128.0.0.0'):", has_interface_with('network', '128.0.0.0')) -info("has_interface_with('netmask', '255.0.0.0'):", has_interface_with('netmask', '255.0.0.0')) -info("has_interface_with('netmask', '256.0.0.0'):", has_interface_with('netmask', '256.0.0.0')) +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/tests/has_ip_address.pp b/tests/has_ip_address.pp index e770a11..8429a88 100644 --- a/tests/has_ip_address.pp +++ b/tests/has_ip_address.pp @@ -1,3 +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')) +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/tests/has_ip_network.pp b/tests/has_ip_network.pp index 036d649..a15d8c0 100644 --- a/tests/has_ip_network.pp +++ b/tests/has_ip_network.pp @@ -1,4 +1,4 @@ include stdlib -info("has_ip_network('127.0.0.0'):", has_ip_network('127.0.0.0')) -info("has_ip_network('128.0.0.0'):", has_ip_network('128.0.0.0')) +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')) |